From e235321cb42b4d60104e0bf6cdf7c6e808c8bc42 Mon Sep 17 00:00:00 2001 From: Youssef Henna Date: Thu, 27 Jul 2023 11:37:13 +0300 Subject: [PATCH 01/21] Delete unused platform specific pickers --- .../Picker/PickerComponent.android.tsx | 116 -------------- .../components/Picker/PickerComponent.ios.tsx | 142 ------------------ .../components/Picker/PickerComponent.web.tsx | 117 --------------- 3 files changed, 375 deletions(-) delete mode 100644 packages/core/src/components/Picker/PickerComponent.android.tsx delete mode 100644 packages/core/src/components/Picker/PickerComponent.ios.tsx delete mode 100644 packages/core/src/components/Picker/PickerComponent.web.tsx diff --git a/packages/core/src/components/Picker/PickerComponent.android.tsx b/packages/core/src/components/Picker/PickerComponent.android.tsx deleted file mode 100644 index 76f059406..000000000 --- a/packages/core/src/components/Picker/PickerComponent.android.tsx +++ /dev/null @@ -1,116 +0,0 @@ -import * as React from "react"; -import { View, StyleSheet } from "react-native"; -import omit from "lodash.omit"; -import { withTheme } from "../../theming"; -import { Picker as NativePicker } from "@react-native-picker/picker"; -import { extractStyles } from "../../utilities"; - -import TextField from "../TextField"; -import Touchable from "../Touchable"; -import { PickerComponentProps } from "./PickerTypes"; - -const Picker: React.FC = ({ - style, - options, - placeholder, - selectedValue, - disabled = false, - onValueChange: onValueChangeOverride = () => {}, - ...props -}) => { - const { - viewStyles: { - borderRadius, // eslint-disable-line @typescript-eslint/no-unused-vars - borderWidth, // eslint-disable-line @typescript-eslint/no-unused-vars - borderTopWidth, // eslint-disable-line @typescript-eslint/no-unused-vars - borderRightWidth, // eslint-disable-line @typescript-eslint/no-unused-vars - borderBottomWidth, // eslint-disable-line @typescript-eslint/no-unused-vars - borderLeftWidth, // eslint-disable-line @typescript-eslint/no-unused-vars - borderColor, // eslint-disable-line @typescript-eslint/no-unused-vars - backgroundColor, // eslint-disable-line @typescript-eslint/no-unused-vars - padding, // eslint-disable-line @typescript-eslint/no-unused-vars - paddingTop, // eslint-disable-line @typescript-eslint/no-unused-vars - paddingRight, // eslint-disable-line @typescript-eslint/no-unused-vars - paddingBottom, // eslint-disable-line @typescript-eslint/no-unused-vars - paddingLeft, // eslint-disable-line @typescript-eslint/no-unused-vars - ...viewStyles - }, - } = extractStyles(style); - - const textField = React.useRef(undefined); - - const onValueChange = (itemValue: string, itemIndex: number) => { - toggleFocus(); - onValueChangeOverride(itemValue, itemIndex); - }; - - const toggleFocus = () => { - if (!disabled) { - // @ts-ignore - textField.current.toggleFocus(); // cannot determine if method exists due to component being wrapped in a withTheme() - } - }; - - const stylesWithoutMargin = - style && - omit(StyleSheet.flatten(style), [ - "margin", - "marginTop", - "marginRight", - "marginBottom", - "marginLeft", - ]); - - const selectedLabel = - selectedValue && - (options.find((o) => o.value === selectedValue)?.label ?? selectedValue); - - return ( - - - - {options.map((o) => ( - - ))} - - - - - - - ); -}; - -const styles = StyleSheet.create({ - container: { - alignSelf: "stretch", - }, -}); - -export default withTheme(Picker); diff --git a/packages/core/src/components/Picker/PickerComponent.ios.tsx b/packages/core/src/components/Picker/PickerComponent.ios.tsx deleted file mode 100644 index ea73a2f8e..000000000 --- a/packages/core/src/components/Picker/PickerComponent.ios.tsx +++ /dev/null @@ -1,142 +0,0 @@ -import * as React from "react"; -import { View, StyleSheet } from "react-native"; -import { SafeAreaView } from "react-native-safe-area-context"; -import omit from "lodash.omit"; - -import { Picker as NativePicker } from "@react-native-picker/picker"; - -import { withTheme } from "../../theming"; - -import Portal from "../Portal/Portal"; -import Button from "../../deprecated-components/DeprecatedButton"; -import TextField from "../TextField"; -import Touchable from "../Touchable"; -import { PickerComponentProps } from "./PickerTypes"; -import { extractStyles } from "../../utilities"; -import type { IconSlot } from "../../interfaces/Icon"; - -const Picker: React.FC = ({ - Icon, - style, - options, - placeholder, - selectedValue, - disabled = false, - onValueChange = () => {}, - theme: { colors }, - ...props -}) => { - const { - viewStyles: { - borderRadius, // eslint-disable-line @typescript-eslint/no-unused-vars - borderWidth, // eslint-disable-line @typescript-eslint/no-unused-vars - borderTopWidth, // eslint-disable-line @typescript-eslint/no-unused-vars - borderRightWidth, // eslint-disable-line @typescript-eslint/no-unused-vars - borderBottomWidth, // eslint-disable-line @typescript-eslint/no-unused-vars - borderLeftWidth, // eslint-disable-line @typescript-eslint/no-unused-vars - borderColor, // eslint-disable-line @typescript-eslint/no-unused-vars - backgroundColor, // eslint-disable-line @typescript-eslint/no-unused-vars - padding, // eslint-disable-line @typescript-eslint/no-unused-vars - paddingTop, // eslint-disable-line @typescript-eslint/no-unused-vars - paddingRight, // eslint-disable-line @typescript-eslint/no-unused-vars - paddingBottom, // eslint-disable-line @typescript-eslint/no-unused-vars - paddingLeft, // eslint-disable-line @typescript-eslint/no-unused-vars - ...viewStyles - }, - } = extractStyles(style); - - const textField = React.useRef(undefined); - const [pickerVisible, setIsPickerVisible] = React.useState(false); - - const toggleVisibility = () => { - setIsPickerVisible(!pickerVisible); - // @ts-ignore - textField.current.toggleFocus(); // cannot determine if method exists due to component being wrapped in a withTheme() - }; - - const stylesWithoutMargin = - style && - omit(StyleSheet.flatten(style), [ - "margin", - "marginTop", - "marginRight", - "marginBottom", - "marginLeft", - ]); - - const selectedLabel = - selectedValue && - (options.find((o) => o.value === selectedValue)?.label ?? selectedValue); - - return ( - - - - - {pickerVisible && ( - - - - - - {options.map((o: any) => ( - - ))} - - - - - )} - - ); -}; - -const styles = StyleSheet.create({ - container: { - alignSelf: "stretch", - }, - picker: { - position: "absolute", - bottom: 0, - left: 0, - right: 0, - flexDirection: "row", - justifyContent: "center", - }, - pickerContainer: { - backgroundColor: "white", - flexDirection: "column", - width: "100%", - }, - closeButton: { - alignSelf: "flex-end", - }, -}); - -export default withTheme(Picker); diff --git a/packages/core/src/components/Picker/PickerComponent.web.tsx b/packages/core/src/components/Picker/PickerComponent.web.tsx deleted file mode 100644 index 3f8fe369e..000000000 --- a/packages/core/src/components/Picker/PickerComponent.web.tsx +++ /dev/null @@ -1,117 +0,0 @@ -import * as React from "react"; -import { View, StyleSheet } from "react-native"; -import { Picker as NativePicker } from "@react-native-picker/picker"; -import omit from "lodash.omit"; -import { withTheme } from "../../theming"; -import { extractStyles } from "../../utilities"; - -import TextField from "../TextField"; -import Touchable from "../Touchable"; -import { PickerComponentProps } from "./PickerTypes"; - -const Picker: React.FC = ({ - style, - options, - placeholder, - selectedValue, - disabled = false, - onValueChange: onValueChangeOverride = () => {}, - ...props -}) => { - const { - viewStyles: { - borderRadius, // eslint-disable-line @typescript-eslint/no-unused-vars - borderWidth, // eslint-disable-line @typescript-eslint/no-unused-vars - borderTopWidth, // eslint-disable-line @typescript-eslint/no-unused-vars - borderRightWidth, // eslint-disable-line @typescript-eslint/no-unused-vars - borderBottomWidth, // eslint-disable-line @typescript-eslint/no-unused-vars - borderLeftWidth, // eslint-disable-line @typescript-eslint/no-unused-vars - borderColor, // eslint-disable-line @typescript-eslint/no-unused-vars - backgroundColor, // eslint-disable-line @typescript-eslint/no-unused-vars - padding, // eslint-disable-line @typescript-eslint/no-unused-vars - paddingTop, // eslint-disable-line @typescript-eslint/no-unused-vars - paddingRight, // eslint-disable-line @typescript-eslint/no-unused-vars - paddingBottom, // eslint-disable-line @typescript-eslint/no-unused-vars - paddingLeft, // eslint-disable-line @typescript-eslint/no-unused-vars - ...viewStyles - }, - } = extractStyles(style); - - const textField = React.useRef(undefined); - - const onValueChange = (itemValue: string, itemIndex: number) => { - toggleFocus(); - onValueChangeOverride(itemValue, itemIndex); - }; - - const toggleFocus = () => { - if (!disabled) { - // @ts-ignore - textField.current.toggleFocus(); // cannot determine if method exists due to component being wrapped in a withTheme() - } - }; - - const stylesWithoutMargin = - style && - omit(StyleSheet.flatten(style), [ - "margin", - "marginTop", - "marginRight", - "marginBottom", - "marginLeft", - ]); - - const selectedLabel = - selectedValue && - (options.find((o) => o.value === selectedValue)?.label ?? selectedValue); - - return ( - - - - {options.map((o) => ( - - ))} - - - - - - - ); -}; - -const styles = StyleSheet.create({ - container: { - alignSelf: "stretch", - }, -}); - -export default withTheme(Picker); From 4e0954a198b6ab01819b1c00b2f3c2f6c2ed749d Mon Sep 17 00:00:00 2001 From: Youssef Henna Date: Thu, 27 Jul 2023 14:57:26 +0300 Subject: [PATCH 02/21] Cleaned up Picker code --- .../core/src/components/Picker/Picker.tsx | 535 +++++------------- packages/core/src/components/TextField.tsx | 22 +- 2 files changed, 148 insertions(+), 409 deletions(-) diff --git a/packages/core/src/components/Picker/Picker.tsx b/packages/core/src/components/Picker/Picker.tsx index 6783a0e50..d429ea1b0 100644 --- a/packages/core/src/components/Picker/Picker.tsx +++ b/packages/core/src/components/Picker/Picker.tsx @@ -2,29 +2,26 @@ import * as React from "react"; import { View, StyleSheet, - Text, Platform, ViewStyle, StyleProp, - Dimensions, Keyboard, } from "react-native"; -import { omit, pickBy, identity, isObject } from "lodash"; +import { isObject } from "lodash"; import { SafeAreaView } from "react-native-safe-area-context"; import { Picker as NativePicker } from "@react-native-picker/picker"; - -import { withTheme } from "../../theming"; import Portal from "../Portal/Portal"; -import Button from "../../deprecated-components/DeprecatedButton"; +import { Button } from "../Button"; import Touchable from "../Touchable"; -import type { Theme } from "../../styles/DefaultTheme"; import type { IconSlot } from "../../interfaces/Icon"; import { - extractStyles, extractBorderAndMarginStyles, - borderStyleNames, - marginStyleNames, + extractSizeStyles, + extractFlexItemStyles, + extractPositionStyles, } from "../../utilities"; +import TextField from "../TextField"; +import omit from "lodash.omit"; export interface PickerOption { value: string; @@ -38,8 +35,7 @@ export type PickerProps = { style?: StyleProp & { height?: number }; value?: string; options: PickerOption[] | string[]; - onValueChange: (value: string, index: number) => void; - defaultValue?: string; + onValueChange: (value: string) => void; assistiveText?: string; label?: string; iconColor?: string; @@ -50,427 +46,145 @@ export type PickerProps = { rightIconName?: string; type?: "solid" | "underline"; autoDismissKeyboard?: boolean; - theme: Theme; - Icon: IconSlot["Icon"]; -}; - -function normalizeOptions(options: PickerProps["options"]): PickerOption[] { - if (options.length === 0) { - return []; - } - - if (typeof options[0] === ("string" || "number")) { - return (options as string[]).map((option) => ({ - label: String(option), - value: String(option), - })); - } - - if ( - isObject(options[0]) && - options[0].value !== null && - options[0].label !== null - ) { - return (options as PickerOption[]).map((option) => { - return { - label: String(option.label), - value: String(option.value), - }; - }); - } +} & IconSlot; - throw new Error( - 'Picker options must be either an array of strings or array of { "label": string; "value": string; } objects.' - ); -} - -const { width: deviceWidth, height: deviceHeight } = Dimensions.get("screen"); const isIos = Platform.OS === "ios"; const isWeb = Platform.OS === "web"; -const unstyledColor = "rgba(165, 173, 183, 1)"; -const disabledColor = "rgb(240, 240, 240)"; -const errorColor = "rgba(255, 69, 100, 1)"; - -//Empty string for 'value' is treated as a non-value -//reason: Draftbit uses empty string as initial value for string state*/ const Picker: React.FC = ({ - error, - options = [], + options: optionsProp = [], onValueChange, - defaultValue, Icon, style, placeholder, value, disabled = false, - assistiveText, - label, - iconColor = unstyledColor, - iconSize = 24, - leftIconMode = "inset", - leftIconName, - placeholderTextColor = unstyledColor, - rightIconName, - type = "solid", autoDismissKeyboard = true, + ...rest }) => { - const androidPickerRef = React.useRef(undefined); - - const [internalValue, setInternalValue] = React.useState( - value || defaultValue - ); + const pickerRef = React.useRef>(null); const [pickerVisible, setPickerVisible] = React.useState(false); - const togglePickerVisible = () => { - setPickerVisible(!pickerVisible); - }; - - React.useEffect(() => { - if (value != null && value !== "") { - setInternalValue(value); - } else if (value === "") { - setInternalValue(undefined); - } - }, [value]); + const options = React.useMemo(() => { + const normalizedOptions = normalizeToPickerOptions(optionsProp); - React.useEffect(() => { - if (defaultValue != null && defaultValue !== "") { - setInternalValue(defaultValue); + // Underlying Picker component defaults selection to first element when value is not provided (or undefined) + // Placholder must be the 1st option in order to allow selection of the 'actual' 1st option + if (placeholder) { + return [{ label: placeholder, value: placeholder }, ...normalizedOptions]; + } else { + return normalizedOptions; } - }, [defaultValue]); + }, [placeholder, optionsProp]); - React.useEffect(() => { - if (pickerVisible && androidPickerRef.current) { - androidPickerRef?.current?.focus(); - } - }, [pickerVisible, androidPickerRef]); - - React.useEffect(() => { - if (pickerVisible && autoDismissKeyboard) { - Keyboard.dismiss(); - } - }, [pickerVisible, autoDismissKeyboard]); - - const normalizedOptions = React.useMemo( - () => normalizeOptions(options), - [options] - ); - - //Underlying Picker component defaults selection to first element when value is not provided (or undefined) - //Placholder must be the 1st option in order to allow selection of the 'actual' 1st option - const pickerOptions = React.useMemo( - () => - placeholder - ? [{ label: placeholder, value: placeholder }, ...normalizedOptions] - : normalizedOptions, - [placeholder, normalizedOptions] - ); - - //When no placeholder is provided then first item should be marked selected to reflect underlying Picker internal state - if ( - !placeholder && - pickerOptions.length && - !internalValue && - internalValue !== pickerOptions[0].value //Prevent infinite state changes incase first value is falsy - ) { - onValueChange?.(pickerOptions[0].value, 0); - setInternalValue(pickerOptions[0].value); + // When no placeholder is provided then first item should be marked selected to reflect underlying Picker internal state + if (!placeholder && options.length && !value && value !== options[0].value) { + onValueChange?.(options[0].value); } - const { viewStyles, textStyles } = extractStyles(style); - - const additionalBorderStyles = ["backgroundColor"]; - - const additionalMarginStyles = [ - "bottom", - "height", - "left", - "maxHeight", - "maxWidth", - "minHeight", - "minWidth", - "overflow", - "position", - "right", - "top", - "width", - "zIndex", - ]; - - const { - borderStyles: extractedBorderStyles, - marginStyles: extractedMarginStyles, - } = extractBorderAndMarginStyles( - viewStyles, - additionalBorderStyles, - additionalMarginStyles - ); - - const borderStyles = { - ...{ - ...(type === "solid" - ? { - borderTopLeftRadius: 5, - borderTopRightRadius: 5, - borderBottomRightRadius: 5, - borderBottomLeftRadius: 5, - borderTopWidth: 1, - borderRightWidth: 1, - borderLeftWidth: 1, - } - : {}), - borderBottomWidth: 1, - borderColor: unstyledColor, - borderStyle: "solid", - }, - ...extractedBorderStyles, - ...(error ? { borderColor: errorColor } : {}), - ...(disabled - ? { borderColor: "transparent", backgroundColor: disabledColor } - : {}), - }; - - const marginStyles = { - height: 60, - ...extractedMarginStyles, - }; - - const stylesWithoutBordersAndMargins = omit(viewStyles, [ - ...borderStyleNames, - ...marginStyleNames, - ...additionalBorderStyles, - ...additionalMarginStyles, - ]); - const selectedLabel = - internalValue && - ((pickerOptions as unknown as PickerOption[]).find( - (option) => option.value === internalValue - )?.label ?? - internalValue); - - const labelText = label ? ( - - {label} - - ) : null; - - const leftIconOutset = leftIconMode === "outset"; - - const leftIcon = leftIconName ? ( - - ) : null; - - const rightIcon = rightIconName ? ( - - ) : null; - - const textAlign = textStyles?.textAlign; - - const calculateLeftPadding = () => { - if (leftIconOutset) { - if (textAlign === "center") { - return iconSize - Math.abs(8 - iconSize); - } - - return iconSize + 8; - } - - return 0; - }; - - const assistiveTextLabel = assistiveText ? ( - option.value === String(value))?.label || + value || + placeholder; + + const containerStyle = StyleSheet.flatten([ + extractSizeStyles(style), + extractPositionStyles(style), + extractFlexItemStyles(style), + extractBorderAndMarginStyles(style).marginStyles, + ]); + const textFieldStyle = omit(style, Object.keys(containerStyle)); + + const renderNativePicker = () => ( + { + if (newValue !== placeholder) { + onValueChange?.(newValue); + } else if (newValue === placeholder) { + onValueChange?.(""); + } }} + style={[ + styles.nativePicker, + isIos ? styles.iosNativePicker : styles.nonIosPicker, + ]} + onBlur={() => setPickerVisible(false)} > - {assistiveText} - - ) : null; - - const primaryTextStyle = { - color: unstyledColor, - fontSize: 14, - ...pickBy(textStyles, identity), - ...(placeholder === internalValue ? { color: placeholderTextColor } : {}), - ...(disabled ? { color: unstyledColor } : {}), - }; - - const handleValueChange = (newValue: string, itemIndex: number) => { - if (newValue !== "" && newValue !== placeholder) { - onValueChange?.(newValue, itemIndex); - setInternalValue(newValue); - } else if (newValue === placeholder) { - onValueChange?.("", 0); - setInternalValue(undefined); - } - }; - - return ( - /* marginsContainer */ - - {/* touchableContainer */} - - {/* outsetContainer */} - - {leftIcon} - - {/* insetContainer */} - - {/* primaryTextContainer */} - - {labelText} - - - {String(selectedLabel ?? placeholder)} - - - - {rightIcon} - - - {assistiveTextLabel} - + {(options as unknown as PickerOption[]).map((option) => ( + + ))} + + ); - {/* iosPicker */} - {isIos && pickerVisible ? ( + const renderPicker = () => { + if (isIos) { + return ( - + - - - {(pickerOptions as unknown as PickerOption[]).map((option) => ( - - ))} - + {renderNativePicker()} - ) : null} + ); + } else { + return renderNativePicker(); + } + }; + + React.useEffect(() => { + if (pickerVisible && pickerRef.current) { + pickerRef?.current?.focus(); + } + }, [pickerVisible, pickerRef]); + + React.useEffect(() => { + if (pickerVisible && autoDismissKeyboard) { + Keyboard.dismiss(); + } + }, [pickerVisible, autoDismissKeyboard]); + + return ( + + setPickerVisible(!pickerVisible)} + > + {}} + value={String(selectedLabel ?? placeholder)} + editable={false} + disabled={disabled} + style={textFieldStyle} + {...rest} + /> + - {/* nonIosPicker */} {/* Web version is collapsed by default, always show to allow direct expand */} - {!isIos && (pickerVisible || isWeb) ? ( - setPickerVisible(false)} - > - {(pickerOptions as unknown as PickerOption[]).map((option) => ( - - ))} - - ) : null} + {(pickerVisible || isWeb) && renderPicker()} ); }; const styles = StyleSheet.create({ - marginsContainer: { - alignSelf: "stretch", - alignItems: "center", - width: "100%", - maxWidth: deviceWidth, - }, - touchableContainer: { - flex: 1, - height: "100%", - width: "100%", - alignSelf: "stretch", - alignItems: "center", - }, - outsetContainer: { - flex: 1, - height: "100%", - width: "100%", - flexDirection: "row", - alignItems: "center", - justifyContent: "space-between", - }, - insetContainer: { - flex: 1, - height: "100%", - width: "100%", - flexDirection: "row", - alignItems: "center", - justifyContent: "space-between", - paddingLeft: 12, - paddingRight: 12, - }, - primaryTextContainer: { - flex: 1, - }, - iosPicker: { + nativePicker: { position: "absolute", bottom: 0, left: 0, @@ -478,14 +192,11 @@ const styles = StyleSheet.create({ flexDirection: "row", justifyContent: "center", width: "100%", - maxWidth: deviceWidth, - maxHeight: deviceHeight, backgroundColor: "white", }, iosPickerContent: { flexDirection: "column", width: "100%", - maxWidth: deviceWidth, }, iosButton: { alignSelf: "flex-end", @@ -495,15 +206,37 @@ const styles = StyleSheet.create({ }, nonIosPicker: { opacity: 0, - position: "absolute", - top: 0, - left: 0, - right: 0, - bottom: 0, - width: "100%", - maxWidth: deviceWidth, - maxHeight: deviceHeight, }, }); -export default withTheme(Picker); +function normalizeToPickerOptions( + options: PickerOption[] | string[] | number[] +): PickerOption[] { + if (options.length === 0) { + return []; + } + + const firstOption = options[0]; + + if (typeof firstOption === ("string" || "number")) { + return options.map((option) => ({ + label: String(option), + value: String(option), + })); + } + + if (isObject(firstOption) && firstOption.value && firstOption.label) { + return (options as PickerOption[]).map((option) => { + return { + label: String(option.label), + value: String(option.value), + }; + }); + } + + throw new Error( + 'Picker options must be either an array of strings or array of { "label": string; "value": string; } objects.' + ); +} + +export default Picker; diff --git a/packages/core/src/components/TextField.tsx b/packages/core/src/components/TextField.tsx index 1cd292124..7ba5f9429 100644 --- a/packages/core/src/components/TextField.tsx +++ b/packages/core/src/components/TextField.tsx @@ -38,12 +38,14 @@ export type Props = { text: string | NativeSyntheticEvent ) => void; rightIconName?: string; + iconColor?: string; + iconSize?: number; assistiveText?: string; multiline?: boolean; numberOfLines: number; underlineColor?: string; activeBorderColor?: string; - style?: StyleProp & { height?: number }; + style?: StyleProp; theme: Theme; render?: ( props: TextInputProps & { ref: (c: NativeTextInput) => void } @@ -241,6 +243,8 @@ class TextField extends React.Component { leftIconName, leftIconMode, rightIconName, + iconColor, + iconSize, assistiveText, underlineColor: underlineColorProp, activeBorderColor: activeBorderColorProp, @@ -278,7 +282,7 @@ class TextField extends React.Component { } else { activeColor = error ? colors.error : activeBorderColorProp; placeholderColor = borderColor = colors.light; - underlineColor = underlineColorProp; + underlineColor = underlineColorProp || colors.light; backgroundColor = colors.background; } @@ -344,7 +348,9 @@ class TextField extends React.Component { } let leftIconColor; - if (error) { + if (iconColor) { + leftIconColor = iconColor; + } else if (error) { leftIconColor = colors.error; } else if (this.state.focused) { leftIconColor = colors.primary; @@ -353,7 +359,7 @@ class TextField extends React.Component { } const leftIconProps = { - size: 24, + size: iconSize || 24, color: leftIconColor, name: leftIconName || "", }; @@ -438,7 +444,7 @@ class TextField extends React.Component { borderLeftWidth, borderColor: borderCol, ...styleProp - } = StyleSheet.flatten(style || {}) as ViewStyle & { height?: number }; + } = StyleSheet.flatten(style) as ViewStyle; return ( @@ -447,7 +453,7 @@ class TextField extends React.Component { ) : null} { {rightIconName ? ( Date: Thu, 27 Jul 2023 15:19:49 +0300 Subject: [PATCH 03/21] Fix TextField failing test --- packages/core/src/components/TextField.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/components/TextField.tsx b/packages/core/src/components/TextField.tsx index 7ba5f9429..024b08ca7 100644 --- a/packages/core/src/components/TextField.tsx +++ b/packages/core/src/components/TextField.tsx @@ -444,7 +444,7 @@ class TextField extends React.Component { borderLeftWidth, borderColor: borderCol, ...styleProp - } = StyleSheet.flatten(style) as ViewStyle; + } = StyleSheet.flatten(style || {}) as ViewStyle; return ( From e7b076fd5c75be8ca0c052acaa9855f257ac7a22 Mon Sep 17 00:00:00 2001 From: Youssef Henna Date: Mon, 14 Aug 2023 11:01:08 +0300 Subject: [PATCH 04/21] More code cleanup and optimizations --- packages/core/package.json | 1 + .../core/src/components/Picker/Picker.tsx | 5 ++-- .../core/src/components/Picker/PickerTypes.ts | 18 --------------- packages/core/src/utilities.ts | 23 ++++++++++++++++++- 4 files changed, 26 insertions(+), 21 deletions(-) delete mode 100644 packages/core/src/components/Picker/PickerTypes.ts diff --git a/packages/core/package.json b/packages/core/package.json index bca8ae143..434f617ce 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -55,6 +55,7 @@ "lodash.isnumber": "^3.0.3", "lodash.omit": "^4.5.0", "lodash.tonumber": "^4.0.3", + "lodash.isequal": "^4.5.0", "react-native-confirmation-code-field": "^7.3.1", "react-native-deck-swiper": "^2.0.12", "react-native-gesture-handler": "~2.9.0", diff --git a/packages/core/src/components/Picker/Picker.tsx b/packages/core/src/components/Picker/Picker.tsx index d429ea1b0..bef4d4681 100644 --- a/packages/core/src/components/Picker/Picker.tsx +++ b/packages/core/src/components/Picker/Picker.tsx @@ -19,6 +19,7 @@ import { extractSizeStyles, extractFlexItemStyles, extractPositionStyles, + useDeepCompareMemo, } from "../../utilities"; import TextField from "../TextField"; import omit from "lodash.omit"; @@ -66,7 +67,7 @@ const Picker: React.FC = ({ const [pickerVisible, setPickerVisible] = React.useState(false); - const options = React.useMemo(() => { + const options = useDeepCompareMemo(() => { const normalizedOptions = normalizeToPickerOptions(optionsProp); // Underlying Picker component defaults selection to first element when value is not provided (or undefined) @@ -113,7 +114,7 @@ const Picker: React.FC = ({ ]} onBlur={() => setPickerVisible(false)} > - {(options as unknown as PickerOption[]).map((option) => ( + {options.map((option) => ( & { height?: number }; - options: PickerOption[]; - placeholder?: string; - selectedValue: string; - disabled?: boolean; - onValueChange?: (value: string, index: number) => void; - defaultValue?: string; -} diff --git a/packages/core/src/utilities.ts b/packages/core/src/utilities.ts index c26b7563c..282abc9f4 100644 --- a/packages/core/src/utilities.ts +++ b/packages/core/src/utilities.ts @@ -1,6 +1,6 @@ import React from "react"; import { StyleSheet, StyleProp, TextStyle } from "react-native"; -import { isString, isNumber, pick, pickBy, identity } from "lodash"; +import { isString, isNumber, pick, pickBy, identity, isEqual } from "lodash"; export function extractStyles(style: StyleProp) { const { @@ -261,3 +261,24 @@ export function flattenReactFragments( return flattened; } + +function useDeepCompareMemoize(value: any) { + const ref = React.useRef(); + + if (!isEqual(value, ref.current)) { + ref.current = value; + } + + return ref.current; +} + +/** + * useMemo counterpart that does a deep compare on the dependency list + */ +export function useDeepCompareMemo( + factory: () => T, + deps: React.DependencyList | undefined +): T { + // eslint-disable-next-line react-hooks/exhaustive-deps + return React.useMemo(factory, deps?.map(useDeepCompareMemoize)); +} From 1c5cd59d445ffe3bf5d2d57733a0b7e5a5266433 Mon Sep 17 00:00:00 2001 From: Youssef Henna Date: Mon, 14 Aug 2023 11:59:22 +0300 Subject: [PATCH 05/21] Moved Picker text field/input into a seperate component --- .../core/src/components/Picker/Picker.tsx | 104 ++++-------------- .../src/components/Picker/PickerCommon.ts | 30 +++++ .../Picker/PickerInputContainer.tsx | 74 +++++++++++++ 3 files changed, 124 insertions(+), 84 deletions(-) create mode 100644 packages/core/src/components/Picker/PickerCommon.ts create mode 100644 packages/core/src/components/Picker/PickerInputContainer.tsx diff --git a/packages/core/src/components/Picker/Picker.tsx b/packages/core/src/components/Picker/Picker.tsx index bef4d4681..6049a44af 100644 --- a/packages/core/src/components/Picker/Picker.tsx +++ b/packages/core/src/components/Picker/Picker.tsx @@ -1,53 +1,13 @@ import * as React from "react"; -import { - View, - StyleSheet, - Platform, - ViewStyle, - StyleProp, - Keyboard, -} from "react-native"; +import { View, StyleSheet, Platform, Keyboard } from "react-native"; import { isObject } from "lodash"; import { SafeAreaView } from "react-native-safe-area-context"; import { Picker as NativePicker } from "@react-native-picker/picker"; import Portal from "../Portal/Portal"; import { Button } from "../Button"; -import Touchable from "../Touchable"; -import type { IconSlot } from "../../interfaces/Icon"; -import { - extractBorderAndMarginStyles, - extractSizeStyles, - extractFlexItemStyles, - extractPositionStyles, - useDeepCompareMemo, -} from "../../utilities"; -import TextField from "../TextField"; -import omit from "lodash.omit"; - -export interface PickerOption { - value: string; - label: string; -} - -export type PickerProps = { - error?: any; - placeholder?: string; - disabled?: boolean; - style?: StyleProp & { height?: number }; - value?: string; - options: PickerOption[] | string[]; - onValueChange: (value: string) => void; - assistiveText?: string; - label?: string; - iconColor?: string; - iconSize?: number; - leftIconMode?: "inset" | "outset"; - leftIconName?: string; - placeholderTextColor?: string; - rightIconName?: string; - type?: "solid" | "underline"; - autoDismissKeyboard?: boolean; -} & IconSlot; +import { useDeepCompareMemo } from "../../utilities"; +import { PickerOption, PickerProps } from "./PickerCommon"; +import PickerInputContainer from "./PickerInputContainer"; const isIos = Platform.OS === "ios"; const isWeb = Platform.OS === "web"; @@ -56,14 +16,12 @@ const Picker: React.FC = ({ options: optionsProp = [], onValueChange, Icon, - style, placeholder, value, - disabled = false, autoDismissKeyboard = true, ...rest }) => { - const pickerRef = React.useRef>(null); + const pickerRef = React.useRef>(null); const [pickerVisible, setPickerVisible] = React.useState(false); @@ -84,19 +42,6 @@ const Picker: React.FC = ({ onValueChange?.(options[0].value); } - const selectedLabel = - options.find((option) => option.value === String(value))?.label || - value || - placeholder; - - const containerStyle = StyleSheet.flatten([ - extractSizeStyles(style), - extractPositionStyles(style), - extractFlexItemStyles(style), - extractBorderAndMarginStyles(style).marginStyles, - ]); - const textFieldStyle = omit(style, Object.keys(containerStyle)); - const renderNativePicker = () => ( = ({ > {options.map((option) => ( @@ -161,26 +106,17 @@ const Picker: React.FC = ({ }, [pickerVisible, autoDismissKeyboard]); return ( - - setPickerVisible(!pickerVisible)} - > - {}} - value={String(selectedLabel ?? placeholder)} - editable={false} - disabled={disabled} - style={textFieldStyle} - {...rest} - /> - - + setPickerVisible(!pickerVisible)} + {...rest} + > {/* Web version is collapsed by default, always show to allow direct expand */} {(pickerVisible || isWeb) && renderPicker()} - + ); }; @@ -221,22 +157,22 @@ function normalizeToPickerOptions( if (typeof firstOption === ("string" || "number")) { return options.map((option) => ({ - label: String(option), - value: String(option), + label: option as string | number, + value: option as string | number, })); } if (isObject(firstOption) && firstOption.value && firstOption.label) { return (options as PickerOption[]).map((option) => { return { - label: String(option.label), - value: String(option.value), + label: option.label, + value: option.value, }; }); } throw new Error( - 'Picker options must be either an array of strings or array of { "label": string; "value": string; } objects.' + 'Picker options must be either an array of strings, numbers, or an array of { "label": string | number; "value": string | number; } objects.' ); } diff --git a/packages/core/src/components/Picker/PickerCommon.ts b/packages/core/src/components/Picker/PickerCommon.ts new file mode 100644 index 000000000..ff2ba67aa --- /dev/null +++ b/packages/core/src/components/Picker/PickerCommon.ts @@ -0,0 +1,30 @@ +import { StyleProp, ViewStyle } from "react-native"; +import { IconSlot } from "../../interfaces/Icon"; + +export interface PickerOption { + value: string | number; + label: string | number; +} + +export interface PickerInputContainerProps extends IconSlot { + error?: any; + placeholder?: string; + disabled?: boolean; + style?: StyleProp; + label?: string; + assistiveText?: string; + iconColor?: string; + iconSize?: number; + leftIconMode?: "inset" | "outset"; + leftIconName?: string; + placeholderTextColor?: string; + rightIconName?: string; + type?: "solid" | "underline"; +} + +export interface PickerProps extends PickerInputContainerProps { + value?: string | number; + options: PickerOption[] | string[] | number[]; + onValueChange: (value: string | number) => void; + autoDismissKeyboard?: boolean; +} diff --git a/packages/core/src/components/Picker/PickerInputContainer.tsx b/packages/core/src/components/Picker/PickerInputContainer.tsx new file mode 100644 index 000000000..d9e337af2 --- /dev/null +++ b/packages/core/src/components/Picker/PickerInputContainer.tsx @@ -0,0 +1,74 @@ +import React from "react"; +import { View, StyleSheet } from "react-native"; +import omit from "lodash.omit"; +import { + extractSizeStyles, + extractPositionStyles, + extractFlexItemStyles, + extractBorderAndMarginStyles, +} from "../../utilities"; +import TextField from "../TextField"; +import Touchable from "../Touchable"; +import { + PickerInputContainerProps as ExposedPickerInputContainerProps, + PickerOption, +} from "./PickerCommon"; + +interface PickerInputContainerProps extends ExposedPickerInputContainerProps { + selectedValue?: string | number; + options: PickerOption[]; + onPress?: () => void; +} + +const PickerInputContainer: React.FC< + React.PropsWithChildren +> = ({ + options = [], + onPress, + Icon, + style, + placeholder, + selectedValue, + disabled = false, + children, + ...rest +}) => { + const containerStyle = StyleSheet.flatten([ + extractSizeStyles(style), + extractPositionStyles(style), + extractFlexItemStyles(style), + extractBorderAndMarginStyles(style).marginStyles, + ]); + + const textFieldStyle = omit( + StyleSheet.flatten(style), + Object.keys(containerStyle) + ); + + const selectedLabel = + options + .find((option) => option.value === selectedValue) + ?.label.toString() || + selectedValue || + placeholder; + + return ( + + + {}} + value={selectedLabel?.toString()} + editable={false} + disabled={disabled} + style={textFieldStyle} + {...rest} + /> + + {children} + + ); +}; + +export default PickerInputContainer; From 49f1c9dec665949e5a799999649fe92e676987be Mon Sep 17 00:00:00 2001 From: Youssef Henna Date: Mon, 14 Aug 2023 12:48:01 +0300 Subject: [PATCH 06/21] Add `react-native-dropdown-picker` --- packages/core/package.json | 3 ++- yarn.lock | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/core/package.json b/packages/core/package.json index 434f617ce..82334b761 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -52,12 +52,13 @@ "date-fns": "^2.16.1", "dateformat": "^3.0.3", "expo-av": "~13.2.1", + "lodash.isequal": "^4.5.0", "lodash.isnumber": "^3.0.3", "lodash.omit": "^4.5.0", "lodash.tonumber": "^4.0.3", - "lodash.isequal": "^4.5.0", "react-native-confirmation-code-field": "^7.3.1", "react-native-deck-swiper": "^2.0.12", + "react-native-dropdown-picker": "^5.4.6", "react-native-gesture-handler": "~2.9.0", "react-native-markdown-display": "^7.0.0-alpha.2", "react-native-modal-datetime-picker": "^13.0.0", diff --git a/yarn.lock b/yarn.lock index 1eff77f00..7b72b302b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15337,6 +15337,11 @@ react-native-deck-swiper@^2.0.12: dependencies: prop-types "15.5.10" +react-native-dropdown-picker@^5.4.6: + version "5.4.6" + resolved "https://registry.yarnpkg.com/react-native-dropdown-picker/-/react-native-dropdown-picker-5.4.6.tgz#3736fc468de4a295e4df8d1f65ed2eadaf9b445f" + integrity sha512-T1XBHbE++M6aRU3wFYw3MvcOuabhWZ29RK/Ivdls2r1ZkZ62iEBZknLUPeVLMX3x6iUxj4Zgr3X2DGlEGXeHsA== + react-native-fit-image@^1.5.5: version "1.5.5" resolved "https://registry.yarnpkg.com/react-native-fit-image/-/react-native-fit-image-1.5.5.tgz#c660d1ad74b9dcaa1cba27a0d9c23837e000226c" From ffc8ef931b7859b328810d41f92359f738d7bfb0 Mon Sep 17 00:00:00 2001 From: Youssef Henna Date: Tue, 15 Aug 2023 18:40:04 +0300 Subject: [PATCH 07/21] Initial implementation of DropDownPicker --- .../{PickerExample.js => PickerExample.tsx} | 94 +++++++++---------- .../src/components/Picker/DropDownPicker.tsx | 63 +++++++++++++ .../Picker/{Picker.tsx => NativePicker.tsx} | 47 ++-------- .../src/components/Picker/PickerCommon.ts | 37 +++++++- packages/core/src/components/Picker/index.tsx | 19 ++++ packages/core/src/index.tsx | 2 +- packages/core/src/utilities.ts | 11 +++ 7 files changed, 178 insertions(+), 95 deletions(-) rename example/src/{PickerExample.js => PickerExample.tsx} (64%) create mode 100644 packages/core/src/components/Picker/DropDownPicker.tsx rename packages/core/src/components/Picker/{Picker.tsx => NativePicker.tsx} (76%) create mode 100644 packages/core/src/components/Picker/index.tsx diff --git a/example/src/PickerExample.js b/example/src/PickerExample.tsx similarity index 64% rename from example/src/PickerExample.js rename to example/src/PickerExample.tsx index b64471102..81583ccfd 100644 --- a/example/src/PickerExample.js +++ b/example/src/PickerExample.tsx @@ -9,56 +9,57 @@ const OPTIONS = [ { value: "DodgeValue", label: "Dodge" }, ]; -function PickerExample({ theme }) { - const [value, setValue] = React.useState("Audi"); +function PickerExample() { + const [value1, setValue] = React.useState("Audi"); const [value2, setValue2] = React.useState("Audi"); const [value3, setValue3] = React.useState(1); return ( - -
+ +
setValue(value.toString())} rightIconName={"AntDesign/caretright"} leftIconName={"AntDesign/caretleft"} leftIconMode="outset" />
-
+
setValue(value.toString())} />
-
+
setValue(value.toString())} />
-
+
setValue(value.toString())} style={{ backgroundColor: "red", padding: 16, @@ -66,72 +67,61 @@ function PickerExample({ theme }) { />
-
+
-
- -
- setValue2(value.toString())} />
-
+
setValue(value.toString())} leftIconName={"AntDesign/caretleft"} leftIconMode="outset" />
-
+
setValue(value.toString())} />
-
+
setValue(value.toString())} />
-
+
setValue(value.toString())} style={{ backgroundColor: "red", borderTopWidth: 2, @@ -143,14 +133,14 @@ function PickerExample({ theme }) { />
-
+
setValue(value.toString())} placeholderTextColor="green" style={{ fontSize: 30, @@ -160,14 +150,14 @@ function PickerExample({ theme }) { />
-
+
setValue(value.toString())} style={{ paddingTop: 25, paddingRight: 25, @@ -177,14 +167,14 @@ function PickerExample({ theme }) { />
-
+
setValue(value.toString())} style={{ marginTop: 25, marginRight: 25, @@ -194,18 +184,18 @@ function PickerExample({ theme }) { />
-
+
setValue2(value.toString())} />
-
+
setValue3(value as number)} />
diff --git a/packages/core/src/components/Picker/DropDownPicker.tsx b/packages/core/src/components/Picker/DropDownPicker.tsx new file mode 100644 index 000000000..f2a59b547 --- /dev/null +++ b/packages/core/src/components/Picker/DropDownPicker.tsx @@ -0,0 +1,63 @@ +import * as React from "react"; +import { Keyboard } from "react-native"; +import { useDeepCompareEffect, useDeepCompareMemo } from "../../utilities"; +import { CommonPickerProps, normalizeToPickerOptions } from "./PickerCommon"; +import PickerInputContainer from "./PickerInputContainer"; +import DropDownPickerComponent from "react-native-dropdown-picker"; + +const DropDownPicker: React.FC = ({ + options: optionsProp = [], + onValueChange, + Icon, + placeholder, + value, + autoDismissKeyboard = true, + ...rest +}) => { + const [pickerVisible, setPickerVisible] = React.useState(false); + const [internalValue, setInternalValue] = React.useState(); + + const options = useDeepCompareMemo( + () => + normalizeToPickerOptions(optionsProp).map((option) => ({ + label: option.label.toString(), + value: option.value, + })), + [optionsProp] + ); + + useDeepCompareEffect(() => { + onValueChange?.(internalValue || ""); + }, [internalValue, onValueChange]); + + React.useEffect(() => { + if (pickerVisible && autoDismissKeyboard) { + Keyboard.dismiss(); + } + }, [pickerVisible, autoDismissKeyboard]); + + return ( + setPickerVisible(!pickerVisible)} + {...rest} + > + + + ); +}; + +// const styles = StyleSheet.create({}); + +export default DropDownPicker; diff --git a/packages/core/src/components/Picker/Picker.tsx b/packages/core/src/components/Picker/NativePicker.tsx similarity index 76% rename from packages/core/src/components/Picker/Picker.tsx rename to packages/core/src/components/Picker/NativePicker.tsx index 6049a44af..fba3fee48 100644 --- a/packages/core/src/components/Picker/Picker.tsx +++ b/packages/core/src/components/Picker/NativePicker.tsx @@ -1,18 +1,17 @@ import * as React from "react"; import { View, StyleSheet, Platform, Keyboard } from "react-native"; -import { isObject } from "lodash"; import { SafeAreaView } from "react-native-safe-area-context"; -import { Picker as NativePicker } from "@react-native-picker/picker"; +import { Picker as NativePickerComponent } from "@react-native-picker/picker"; import Portal from "../Portal/Portal"; import { Button } from "../Button"; import { useDeepCompareMemo } from "../../utilities"; -import { PickerOption, PickerProps } from "./PickerCommon"; +import { CommonPickerProps, normalizeToPickerOptions } from "./PickerCommon"; import PickerInputContainer from "./PickerInputContainer"; const isIos = Platform.OS === "ios"; const isWeb = Platform.OS === "web"; -const Picker: React.FC = ({ +const NativePicker: React.FC = ({ options: optionsProp = [], onValueChange, Icon, @@ -21,7 +20,7 @@ const Picker: React.FC = ({ autoDismissKeyboard = true, ...rest }) => { - const pickerRef = React.useRef>(null); + const pickerRef = React.useRef>(null); const [pickerVisible, setPickerVisible] = React.useState(false); @@ -43,7 +42,7 @@ const Picker: React.FC = ({ } const renderNativePicker = () => ( - { @@ -60,13 +59,13 @@ const Picker: React.FC = ({ onBlur={() => setPickerVisible(false)} > {options.map((option) => ( - ))} - + ); const renderPicker = () => { @@ -146,34 +145,4 @@ const styles = StyleSheet.create({ }, }); -function normalizeToPickerOptions( - options: PickerOption[] | string[] | number[] -): PickerOption[] { - if (options.length === 0) { - return []; - } - - const firstOption = options[0]; - - if (typeof firstOption === ("string" || "number")) { - return options.map((option) => ({ - label: option as string | number, - value: option as string | number, - })); - } - - if (isObject(firstOption) && firstOption.value && firstOption.label) { - return (options as PickerOption[]).map((option) => { - return { - label: option.label, - value: option.value, - }; - }); - } - - throw new Error( - 'Picker options must be either an array of strings, numbers, or an array of { "label": string | number; "value": string | number; } objects.' - ); -} - -export default Picker; +export default NativePicker; diff --git a/packages/core/src/components/Picker/PickerCommon.ts b/packages/core/src/components/Picker/PickerCommon.ts index ff2ba67aa..8477c4de8 100644 --- a/packages/core/src/components/Picker/PickerCommon.ts +++ b/packages/core/src/components/Picker/PickerCommon.ts @@ -1,5 +1,6 @@ -import { StyleProp, ViewStyle } from "react-native"; +import { StyleProp, ViewStyle, TextStyle } from "react-native"; import { IconSlot } from "../../interfaces/Icon"; +import { isObject } from "lodash"; export interface PickerOption { value: string | number; @@ -10,7 +11,7 @@ export interface PickerInputContainerProps extends IconSlot { error?: any; placeholder?: string; disabled?: boolean; - style?: StyleProp; + style?: StyleProp | TextStyle; label?: string; assistiveText?: string; iconColor?: string; @@ -22,9 +23,39 @@ export interface PickerInputContainerProps extends IconSlot { type?: "solid" | "underline"; } -export interface PickerProps extends PickerInputContainerProps { +export interface CommonPickerProps extends PickerInputContainerProps { value?: string | number; options: PickerOption[] | string[] | number[]; onValueChange: (value: string | number) => void; autoDismissKeyboard?: boolean; } + +export function normalizeToPickerOptions( + options: PickerOption[] | string[] | number[] +): PickerOption[] { + if (options.length === 0) { + return []; + } + + const firstOption = options[0]; + + if (typeof firstOption === ("string" || "number")) { + return options.map((option) => ({ + label: option as string | number, + value: option as string | number, + })); + } + + if (isObject(firstOption) && firstOption.value && firstOption.label) { + return (options as PickerOption[]).map((option) => { + return { + label: option.label, + value: option.value, + }; + }); + } + + throw new Error( + 'Picker options must be either an array of strings, numbers, or an array of { "label": string | number; "value": string | number; } objects.' + ); +} diff --git a/packages/core/src/components/Picker/index.tsx b/packages/core/src/components/Picker/index.tsx new file mode 100644 index 000000000..35e7c5728 --- /dev/null +++ b/packages/core/src/components/Picker/index.tsx @@ -0,0 +1,19 @@ +import React from "react"; +import { CommonPickerProps } from "./PickerCommon"; +import NativePicker from "./NativePicker"; +import DropDownPicker from "./DropDownPicker"; + +interface PickerProps extends CommonPickerProps { + mode?: "native" | "dropdown"; +} + +const Picker: React.FC = ({ mode = "native", ...rest }) => { + switch (mode) { + case "native": + return ; + case "dropdown": + return ; + } +}; + +export default Picker; diff --git a/packages/core/src/index.tsx b/packages/core/src/index.tsx index f6c52255e..b8235b357 100644 --- a/packages/core/src/index.tsx +++ b/packages/core/src/index.tsx @@ -47,7 +47,7 @@ export { AudioPlayerRef, } from "./components/MediaPlayer/AudioPlayer"; export { default as DatePicker } from "./components/DatePicker/DatePicker"; -export { default as Picker } from "./components/Picker/Picker"; +export { default as Picker } from "./components/Picker"; export { default as Slider } from "./components/Slider"; export { default as Stepper } from "./components/Stepper"; export { SectionList, SectionHeader } from "./components/SectionList"; diff --git a/packages/core/src/utilities.ts b/packages/core/src/utilities.ts index 282abc9f4..5066ddfa9 100644 --- a/packages/core/src/utilities.ts +++ b/packages/core/src/utilities.ts @@ -282,3 +282,14 @@ export function useDeepCompareMemo( // eslint-disable-next-line react-hooks/exhaustive-deps return React.useMemo(factory, deps?.map(useDeepCompareMemoize)); } + +/** + * useMemo counterpart that does a deep compare on the dependency list + */ +export function useDeepCompareEffect( + effect: React.EffectCallback, + deps: React.DependencyList | undefined +) { + // eslint-disable-next-line react-hooks/exhaustive-deps + return React.useEffect(effect, deps?.map(useDeepCompareMemoize)); +} From fa9c5243d9acf8802d43fb7a9c98c4d53d7050c2 Mon Sep 17 00:00:00 2001 From: Youssef Henna Date: Thu, 17 Aug 2023 14:58:35 +0300 Subject: [PATCH 08/21] Figured out how to render dropdown above sibling components --- example/src/PickerExample.tsx | 25 ++++++++++--------- .../src/components/Picker/DropDownPicker.tsx | 3 +++ .../Picker/PickerInputContainer.tsx | 4 ++- 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/example/src/PickerExample.tsx b/example/src/PickerExample.tsx index 81583ccfd..54cc0a577 100644 --- a/example/src/PickerExample.tsx +++ b/example/src/PickerExample.tsx @@ -17,18 +17,19 @@ function PickerExample() { return (
- setValue(value.toString())} - rightIconName={"AntDesign/caretright"} - leftIconName={"AntDesign/caretleft"} - leftIconMode="outset" - /> + <>
+ setValue(value.toString())} + rightIconName={"AntDesign/caretright"} + leftIconName={"AntDesign/caretleft"} + leftIconMode="outset" + />
setValue(value.toString())} />
diff --git a/packages/core/src/components/Picker/DropDownPicker.tsx b/packages/core/src/components/Picker/DropDownPicker.tsx index f2a59b547..0b8764d10 100644 --- a/packages/core/src/components/Picker/DropDownPicker.tsx +++ b/packages/core/src/components/Picker/DropDownPicker.tsx @@ -43,6 +43,7 @@ const DropDownPicker: React.FC = ({ selectedValue={value} options={options} onPress={() => setPickerVisible(!pickerVisible)} + zIndex={pickerVisible ? 100 : undefined} // Guarantees drop down is rendered above all sibling components {...rest} > = ({ items={options} placeholder={placeholder} listMode="SCROLLVIEW" + style={{ display: "none" }} // This is the style of the input container + dropDownContainerStyle={{}} /> ); diff --git a/packages/core/src/components/Picker/PickerInputContainer.tsx b/packages/core/src/components/Picker/PickerInputContainer.tsx index d9e337af2..c0a57ad2d 100644 --- a/packages/core/src/components/Picker/PickerInputContainer.tsx +++ b/packages/core/src/components/Picker/PickerInputContainer.tsx @@ -17,6 +17,7 @@ import { interface PickerInputContainerProps extends ExposedPickerInputContainerProps { selectedValue?: string | number; options: PickerOption[]; + zIndex?: number; onPress?: () => void; } @@ -30,6 +31,7 @@ const PickerInputContainer: React.FC< placeholder, selectedValue, disabled = false, + zIndex, children, ...rest }) => { @@ -53,7 +55,7 @@ const PickerInputContainer: React.FC< placeholder; return ( - + Date: Thu, 17 Aug 2023 17:11:22 +0300 Subject: [PATCH 09/21] Update some default styling and icon usage --- .../src/components/Picker/DropDownPicker.tsx | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/packages/core/src/components/Picker/DropDownPicker.tsx b/packages/core/src/components/Picker/DropDownPicker.tsx index 0b8764d10..fecadaa5c 100644 --- a/packages/core/src/components/Picker/DropDownPicker.tsx +++ b/packages/core/src/components/Picker/DropDownPicker.tsx @@ -4,14 +4,25 @@ import { useDeepCompareEffect, useDeepCompareMemo } from "../../utilities"; import { CommonPickerProps, normalizeToPickerOptions } from "./PickerCommon"; import PickerInputContainer from "./PickerInputContainer"; import DropDownPickerComponent from "react-native-dropdown-picker"; +import { withTheme } from "../../theming"; +import { Theme } from "../../styles/DefaultTheme"; -const DropDownPicker: React.FC = ({ +interface DropDownPickerProps extends CommonPickerProps { + selectedIconName?: string; + selectedIconColor?: string; + theme: Theme; +} + +const DropDownPicker: React.FC = ({ + theme, options: optionsProp = [], onValueChange, Icon, placeholder, value, autoDismissKeyboard = true, + selectedIconColor = theme.colors.strong, + selectedIconName = "Feather/check", ...rest }) => { const [pickerVisible, setPickerVisible] = React.useState(false); @@ -54,8 +65,15 @@ const DropDownPicker: React.FC = ({ items={options} placeholder={placeholder} listMode="SCROLLVIEW" - style={{ display: "none" }} // This is the style of the input container - dropDownContainerStyle={{}} + style={{ display: "none" }} // To not render the default input container + dropDownContainerStyle={{ + borderColor: theme.colors.divider, + backgroundColor: theme.colors.background, + }} + textStyle={{ color: theme.colors.strong }} + TickIconComponent={() => ( + + )} /> ); @@ -63,4 +81,4 @@ const DropDownPicker: React.FC = ({ // const styles = StyleSheet.create({}); -export default DropDownPicker; +export default withTheme(DropDownPicker); From 8ba25206da883de7a577b422819cf3097f866e9d Mon Sep 17 00:00:00 2001 From: Youssef Henna Date: Sun, 20 Aug 2023 13:20:16 +0300 Subject: [PATCH 10/21] Finalized styling props + added multi select picker --- example/src/PickerExample.tsx | 17 ++- .../src/components/Picker/DropDownPicker.tsx | 84 ------------ .../src/components/Picker/NativePicker.tsx | 8 +- .../src/components/Picker/PickerCommon.ts | 30 ++++- .../Picker/PickerInputContainer.tsx | 25 ++-- .../Picker/dropdown/DropDownPicker.tsx | 122 ++++++++++++++++++ .../Picker/dropdown/MultiSelectPicker.tsx | 16 +++ packages/core/src/components/Picker/index.tsx | 13 +- packages/core/src/index.tsx | 2 +- packages/ui/src/index.tsx | 2 + 10 files changed, 215 insertions(+), 104 deletions(-) delete mode 100644 packages/core/src/components/Picker/DropDownPicker.tsx create mode 100644 packages/core/src/components/Picker/dropdown/DropDownPicker.tsx create mode 100644 packages/core/src/components/Picker/dropdown/MultiSelectPicker.tsx diff --git a/example/src/PickerExample.tsx b/example/src/PickerExample.tsx index 54cc0a577..0ac769e89 100644 --- a/example/src/PickerExample.tsx +++ b/example/src/PickerExample.tsx @@ -1,18 +1,24 @@ import * as React from "react"; -import { Picker, withTheme } from "@draftbit/ui"; +import { Picker, MultiSelectPicker, withTheme } from "@draftbit/ui"; import Section, { Container } from "./Section"; const OPTIONS = [ { value: "AudiValue", label: "Audi" }, { value: "BMWValue", label: "BMW" }, - { value: "CadillacValue", label: "Cadillac" }, + { + value: "CadillacValue", + label: "Cadillac", + }, { value: "DodgeValue", label: "Dodge" }, + { value: "KiaValue", label: "Kia" }, + { value: "HyundaiValue", label: "Hyundai" }, ]; function PickerExample() { const [value1, setValue] = React.useState("Audi"); const [value2, setValue2] = React.useState("Audi"); const [value3, setValue3] = React.useState(1); + const [value4, setValue4] = React.useState<(string | number)[]>([]); return ( @@ -30,6 +36,13 @@ function PickerExample() { leftIconName={"AntDesign/caretleft"} leftIconMode="outset" /> + setValue4(value)} + />
= ({ - theme, - options: optionsProp = [], - onValueChange, - Icon, - placeholder, - value, - autoDismissKeyboard = true, - selectedIconColor = theme.colors.strong, - selectedIconName = "Feather/check", - ...rest -}) => { - const [pickerVisible, setPickerVisible] = React.useState(false); - const [internalValue, setInternalValue] = React.useState(); - - const options = useDeepCompareMemo( - () => - normalizeToPickerOptions(optionsProp).map((option) => ({ - label: option.label.toString(), - value: option.value, - })), - [optionsProp] - ); - - useDeepCompareEffect(() => { - onValueChange?.(internalValue || ""); - }, [internalValue, onValueChange]); - - React.useEffect(() => { - if (pickerVisible && autoDismissKeyboard) { - Keyboard.dismiss(); - } - }, [pickerVisible, autoDismissKeyboard]); - - return ( - setPickerVisible(!pickerVisible)} - zIndex={pickerVisible ? 100 : undefined} // Guarantees drop down is rendered above all sibling components - {...rest} - > - ( - - )} - /> - - ); -}; - -// const styles = StyleSheet.create({}); - -export default withTheme(DropDownPicker); diff --git a/packages/core/src/components/Picker/NativePicker.tsx b/packages/core/src/components/Picker/NativePicker.tsx index fba3fee48..3f37105a3 100644 --- a/packages/core/src/components/Picker/NativePicker.tsx +++ b/packages/core/src/components/Picker/NativePicker.tsx @@ -5,13 +5,17 @@ import { Picker as NativePickerComponent } from "@react-native-picker/picker"; import Portal from "../Portal/Portal"; import { Button } from "../Button"; import { useDeepCompareMemo } from "../../utilities"; -import { CommonPickerProps, normalizeToPickerOptions } from "./PickerCommon"; +import { + CommonPickerProps, + SinglePickerProps, + normalizeToPickerOptions, +} from "./PickerCommon"; import PickerInputContainer from "./PickerInputContainer"; const isIos = Platform.OS === "ios"; const isWeb = Platform.OS === "web"; -const NativePicker: React.FC = ({ +const NativePicker: React.FC = ({ options: optionsProp = [], onValueChange, Icon, diff --git a/packages/core/src/components/Picker/PickerCommon.ts b/packages/core/src/components/Picker/PickerCommon.ts index 8477c4de8..d20f4f992 100644 --- a/packages/core/src/components/Picker/PickerCommon.ts +++ b/packages/core/src/components/Picker/PickerCommon.ts @@ -1,6 +1,7 @@ import { StyleProp, ViewStyle, TextStyle } from "react-native"; import { IconSlot } from "../../interfaces/Icon"; import { isObject } from "lodash"; +import { Theme } from "../../styles/DefaultTheme"; export interface PickerOption { value: string | number; @@ -24,12 +25,37 @@ export interface PickerInputContainerProps extends IconSlot { } export interface CommonPickerProps extends PickerInputContainerProps { - value?: string | number; options: PickerOption[] | string[] | number[]; - onValueChange: (value: string | number) => void; autoDismissKeyboard?: boolean; } +export interface SinglePickerProps { + value?: string | number; + onValueChange: (value: string | number) => void; +} + +export interface MultiSelectPickerProps { + value?: (string | number)[]; + onValueChange: (value: (string | number)[]) => void; +} + +export interface CommonDropDownPickerProps extends CommonPickerProps { + selectedIconName?: string; + selectedIconColor?: string; + selectedIconSize?: number; + itemTextSize?: number; + itemTextColor?: string; + itemBackgroundColor?: string; + selectedItemTextSize?: number; + selectedItemTextColor?: string; + selectedItemBackgroundColor?: string; + dropDownBackgroundColor?: string; + dropDownBorderColor?: string; + dropDownBorderWidth?: number; + dropDownBorderRadius?: number; + theme: Theme; +} + export function normalizeToPickerOptions( options: PickerOption[] | string[] | number[] ): PickerOption[] { diff --git a/packages/core/src/components/Picker/PickerInputContainer.tsx b/packages/core/src/components/Picker/PickerInputContainer.tsx index c0a57ad2d..de38c956b 100644 --- a/packages/core/src/components/Picker/PickerInputContainer.tsx +++ b/packages/core/src/components/Picker/PickerInputContainer.tsx @@ -15,7 +15,7 @@ import { } from "./PickerCommon"; interface PickerInputContainerProps extends ExposedPickerInputContainerProps { - selectedValue?: string | number; + selectedValue?: string | number | (string | number)[]; options: PickerOption[]; zIndex?: number; onPress?: () => void; @@ -47,12 +47,21 @@ const PickerInputContainer: React.FC< Object.keys(containerStyle) ); - const selectedLabel = - options - .find((option) => option.value === selectedValue) - ?.label.toString() || - selectedValue || - placeholder; + let selectedLabel: string | number | undefined = ""; + if (Array.isArray(selectedValue)) { + selectedLabel = selectedValue + .map( + (value) => + options.find((option) => option.value === value)?.label.toString() || + value + ) + .join(", "); + } else { + selectedLabel = + options + .find((option) => option.value === selectedValue) + ?.label.toString() || selectedValue; + } return ( @@ -61,7 +70,7 @@ const PickerInputContainer: React.FC< Icon={Icon} numberOfLines={1} onChangeText={() => {}} - value={selectedLabel?.toString()} + value={selectedLabel?.toString() || placeholder} editable={false} disabled={disabled} style={textFieldStyle} diff --git a/packages/core/src/components/Picker/dropdown/DropDownPicker.tsx b/packages/core/src/components/Picker/dropdown/DropDownPicker.tsx new file mode 100644 index 000000000..7caea3f31 --- /dev/null +++ b/packages/core/src/components/Picker/dropdown/DropDownPicker.tsx @@ -0,0 +1,122 @@ +import * as React from "react"; +import { Keyboard } from "react-native"; +import { extractStyles, useDeepCompareMemo } from "../../../utilities"; +import { + CommonDropDownPickerProps, + MultiSelectPickerProps, + SinglePickerProps, + normalizeToPickerOptions, +} from "../PickerCommon"; +import PickerInputContainer from "../PickerInputContainer"; +import DropDownPickerComponent from "react-native-dropdown-picker"; +import { withTheme } from "../../../theming"; + +const DropDownPicker: React.FC< + CommonDropDownPickerProps & (SinglePickerProps | MultiSelectPickerProps) +> = ({ + theme, + options: optionsProp = [], + onValueChange, + Icon, + placeholder, + value, + autoDismissKeyboard = true, + style, + selectedIconName = "Feather/check", + selectedIconColor = theme.colors.strong, + selectedIconSize = 20, + itemTextSize = 14, + itemTextColor = theme.colors.strong, + itemBackgroundColor, + selectedItemTextSize = itemTextSize, + selectedItemTextColor = itemTextColor, + selectedItemBackgroundColor = itemBackgroundColor, + dropDownBackgroundColor = theme.colors.background, + dropDownBorderColor = theme.colors.divider, + dropDownBorderWidth = 1, + dropDownBorderRadius = 8, + ...rest +}) => { + const [pickerVisible, setPickerVisible] = React.useState(false); + const [internalValue, setInternalValue] = React.useState< + string | number | (string | number)[] + >(); + + const isMultiSelect = Array.isArray(value); + + const options = useDeepCompareMemo( + () => + normalizeToPickerOptions(optionsProp).map((option) => ({ + label: option.label.toString(), + value: option.value, + })), + [optionsProp] + ); + + const { textStyles } = extractStyles(style); + + React.useEffect(() => { + onValueChange?.( + (isMultiSelect ? internalValue ?? [] : internalValue ?? "") as any // cannot determine if multiselect or not on compile time + ); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [internalValue]); + + React.useEffect(() => { + if (pickerVisible && autoDismissKeyboard) { + Keyboard.dismiss(); + } + }, [pickerVisible, autoDismissKeyboard]); + + return ( + setPickerVisible(!pickerVisible)} + zIndex={pickerVisible ? 100 : undefined} // Guarantees drop down is rendered above all sibling components + {...rest} + > + ( + + )} + /> + + ); +}; + +export default withTheme(DropDownPicker); diff --git a/packages/core/src/components/Picker/dropdown/MultiSelectPicker.tsx b/packages/core/src/components/Picker/dropdown/MultiSelectPicker.tsx new file mode 100644 index 000000000..f4035a5a7 --- /dev/null +++ b/packages/core/src/components/Picker/dropdown/MultiSelectPicker.tsx @@ -0,0 +1,16 @@ +import * as React from "react"; +import { + CommonDropDownPickerProps, + MultiSelectPickerProps, +} from "../PickerCommon"; +import DropDownPicker from "./DropDownPicker"; +import { withTheme } from "../../../theming"; + +const MultiSelectPicker: React.FC< + CommonDropDownPickerProps & MultiSelectPickerProps +> = ({ value, ...rest }) => { + //@ts-ignore Ignore theme type issues + return ; +}; + +export default withTheme(MultiSelectPicker); diff --git a/packages/core/src/components/Picker/index.tsx b/packages/core/src/components/Picker/index.tsx index 35e7c5728..aaf2b6743 100644 --- a/packages/core/src/components/Picker/index.tsx +++ b/packages/core/src/components/Picker/index.tsx @@ -1,19 +1,22 @@ import React from "react"; -import { CommonPickerProps } from "./PickerCommon"; +import { CommonDropDownPickerProps, SinglePickerProps } from "./PickerCommon"; import NativePicker from "./NativePicker"; -import DropDownPicker from "./DropDownPicker"; +import DropDownPicker from "./dropdown/DropDownPicker"; +import { withTheme } from "../../theming"; -interface PickerProps extends CommonPickerProps { +interface PickerProps extends CommonDropDownPickerProps, SinglePickerProps { mode?: "native" | "dropdown"; } -const Picker: React.FC = ({ mode = "native", ...rest }) => { +const SinglePicker: React.FC = ({ mode = "native", ...rest }) => { switch (mode) { case "native": return ; case "dropdown": + //@ts-ignore Ignore theme type issues return ; } }; -export default Picker; +export const Picker = withTheme(SinglePicker); +export { default as MultiSelectPicker } from "./dropdown/MultiSelectPicker"; diff --git a/packages/core/src/index.tsx b/packages/core/src/index.tsx index b8235b357..b3bfbc7d5 100644 --- a/packages/core/src/index.tsx +++ b/packages/core/src/index.tsx @@ -47,7 +47,7 @@ export { AudioPlayerRef, } from "./components/MediaPlayer/AudioPlayer"; export { default as DatePicker } from "./components/DatePicker/DatePicker"; -export { default as Picker } from "./components/Picker"; +export { Picker, MultiSelectPicker } from "./components/Picker"; export { default as Slider } from "./components/Slider"; export { default as Stepper } from "./components/Stepper"; export { SectionList, SectionHeader } from "./components/SectionList"; diff --git a/packages/ui/src/index.tsx b/packages/ui/src/index.tsx index 08c2acbe1..2c9e192e3 100644 --- a/packages/ui/src/index.tsx +++ b/packages/ui/src/index.tsx @@ -91,6 +91,7 @@ import { FieldSearchBarFull as BaseFieldSearchBarFull, IconButton as BaseIconButton, Picker as BasePicker, + MultiSelectPicker as BaseMultiSelectPicker, StarRating as BaseStarRating, TextField as BaseTextField, RadioButton as BaseRadioButton, @@ -120,6 +121,7 @@ export const FieldSearchBarFull = injectIcon(BaseFieldSearchBarFull, Icon); export const IconButton = injectIcon(BaseIconButton, Icon); export const Link = injectIcon(BaseLink, Icon); export const Picker = injectIcon(BasePicker, Icon); +export const MultiSelectPicker = injectIcon(BaseMultiSelectPicker, Icon); export const RadioButton = injectIcon(BaseRadioButton, Icon); export const RadioButtonRow = injectIcon(BaseRadioButtonRow, Icon); export const RowBodyIcon = injectIcon(BaseRowBodyIcon, Icon); From 241545508133c200c18016972266fe08a73bf128 Mon Sep 17 00:00:00 2001 From: Youssef Henna Date: Sun, 27 Aug 2023 13:25:41 +0300 Subject: [PATCH 11/21] Extract dropdown item styles to a seperate component --- .../Picker/dropdown/DropDownPicker.tsx | 72 +++++++++++++------ .../components/Picker/dropdown/PickerItem.tsx | 19 +++++ packages/core/src/components/Picker/index.tsx | 1 + packages/core/src/index.tsx | 2 +- packages/ui/src/index.tsx | 1 + 5 files changed, 72 insertions(+), 23 deletions(-) create mode 100644 packages/core/src/components/Picker/dropdown/PickerItem.tsx diff --git a/packages/core/src/components/Picker/dropdown/DropDownPicker.tsx b/packages/core/src/components/Picker/dropdown/DropDownPicker.tsx index 7caea3f31..d5e82ba02 100644 --- a/packages/core/src/components/Picker/dropdown/DropDownPicker.tsx +++ b/packages/core/src/components/Picker/dropdown/DropDownPicker.tsx @@ -1,6 +1,10 @@ import * as React from "react"; import { Keyboard } from "react-native"; -import { extractStyles, useDeepCompareMemo } from "../../../utilities"; +import { + extractStyles, + flattenReactFragments, + useDeepCompareMemo, +} from "../../../utilities"; import { CommonDropDownPickerProps, MultiSelectPickerProps, @@ -10,9 +14,12 @@ import { import PickerInputContainer from "../PickerInputContainer"; import DropDownPickerComponent from "react-native-dropdown-picker"; import { withTheme } from "../../../theming"; +import PickerItem, { PickerItemProps } from "./PickerItem"; const DropDownPicker: React.FC< - CommonDropDownPickerProps & (SinglePickerProps | MultiSelectPickerProps) + React.PropsWithChildren< + CommonDropDownPickerProps & (SinglePickerProps | MultiSelectPickerProps) + > > = ({ theme, options: optionsProp = [], @@ -21,20 +28,14 @@ const DropDownPicker: React.FC< placeholder, value, autoDismissKeyboard = true, - style, selectedIconName = "Feather/check", selectedIconColor = theme.colors.strong, selectedIconSize = 20, - itemTextSize = 14, - itemTextColor = theme.colors.strong, - itemBackgroundColor, - selectedItemTextSize = itemTextSize, - selectedItemTextColor = itemTextColor, - selectedItemBackgroundColor = itemBackgroundColor, dropDownBackgroundColor = theme.colors.background, dropDownBorderColor = theme.colors.divider, dropDownBorderWidth = 1, dropDownBorderRadius = 8, + children: childrenProp, ...rest }) => { const [pickerVisible, setPickerVisible] = React.useState(false); @@ -44,6 +45,24 @@ const DropDownPicker: React.FC< const isMultiSelect = Array.isArray(value); + const pickerItemProps: PickerItemProps = React.useMemo(() => { + const children = flattenReactFragments( + React.Children.toArray(childrenProp) as React.ReactElement[] + ); + + let firstPickerItem; // Only the props of the first PickerItem are used, any others are ignored + for (const child of children) { + if (child.type === PickerItem) { + firstPickerItem = child; + } + } + + return firstPickerItem?.props || {}; + }, [childrenProp]); + + const { viewStyles: pickerItemViewStyles, textStyles: pickerItemTextStyles } = + extractStyles(pickerItemProps.style); + const options = useDeepCompareMemo( () => normalizeToPickerOptions(optionsProp).map((option) => ({ @@ -53,8 +72,6 @@ const DropDownPicker: React.FC< [optionsProp] ); - const { textStyles } = extractStyles(style); - React.useEffect(() => { onValueChange?.( (isMultiSelect ? internalValue ?? [] : internalValue ?? "") as any // cannot determine if multiselect or not on compile time @@ -70,7 +87,6 @@ const DropDownPicker: React.FC< return ( ; +} + +/** + * Renders nothing, only serves as a container for the props + * Prop values are used by the DropDownPicker + */ +export const PickerItem: React.FC = () => { + return null; +}; + +export default PickerItem; diff --git a/packages/core/src/components/Picker/index.tsx b/packages/core/src/components/Picker/index.tsx index aaf2b6743..858e38614 100644 --- a/packages/core/src/components/Picker/index.tsx +++ b/packages/core/src/components/Picker/index.tsx @@ -20,3 +20,4 @@ const SinglePicker: React.FC = ({ mode = "native", ...rest }) => { export const Picker = withTheme(SinglePicker); export { default as MultiSelectPicker } from "./dropdown/MultiSelectPicker"; +export { default as PickerItem } from "./dropdown/PickerItem"; diff --git a/packages/core/src/index.tsx b/packages/core/src/index.tsx index b3bfbc7d5..e1b28eb16 100644 --- a/packages/core/src/index.tsx +++ b/packages/core/src/index.tsx @@ -47,7 +47,7 @@ export { AudioPlayerRef, } from "./components/MediaPlayer/AudioPlayer"; export { default as DatePicker } from "./components/DatePicker/DatePicker"; -export { Picker, MultiSelectPicker } from "./components/Picker"; +export { Picker, MultiSelectPicker, PickerItem } from "./components/Picker"; export { default as Slider } from "./components/Slider"; export { default as Stepper } from "./components/Stepper"; export { SectionList, SectionHeader } from "./components/SectionList"; diff --git a/packages/ui/src/index.tsx b/packages/ui/src/index.tsx index 2c9e192e3..72a3d8463 100644 --- a/packages/ui/src/index.tsx +++ b/packages/ui/src/index.tsx @@ -66,6 +66,7 @@ export { HStack, VStack, ZStack, + PickerItem, } from "@draftbit/core"; /** From c9bd878a61610e2a42288fe860619afa9d5559a6 Mon Sep 17 00:00:00 2001 From: Youssef Henna Date: Sun, 27 Aug 2023 17:23:35 +0300 Subject: [PATCH 12/21] Final tweaks and fixes + update example --- example/src/PickerExample.tsx | 139 ++++-------------- .../src/components/Picker/NativePicker.tsx | 5 + .../Picker/PickerInputContainer.tsx | 28 ++-- .../Picker/dropdown/MultiSelectPicker.tsx | 2 +- packages/core/src/components/Picker/index.tsx | 5 +- packages/core/src/components/TextField.tsx | 4 +- 6 files changed, 58 insertions(+), 125 deletions(-) diff --git a/example/src/PickerExample.tsx b/example/src/PickerExample.tsx index 0ac769e89..7ed09d941 100644 --- a/example/src/PickerExample.tsx +++ b/example/src/PickerExample.tsx @@ -1,5 +1,5 @@ import * as React from "react"; -import { Picker, MultiSelectPicker, withTheme } from "@draftbit/ui"; +import { Picker, MultiSelectPicker, PickerItem, withTheme } from "@draftbit/ui"; import Section, { Container } from "./Section"; const OPTIONS = [ @@ -16,13 +16,13 @@ const OPTIONS = [ function PickerExample() { const [value1, setValue] = React.useState("Audi"); - const [value2, setValue2] = React.useState("Audi"); const [value3, setValue3] = React.useState(1); const [value4, setValue4] = React.useState<(string | number)[]>([]); return ( -
+ {/* Dropdown and Multiselect placed outside Section to be able to draw over sibling components */} +
<>
setValue(value.toString())} - rightIconName={"AntDesign/caretright"} - leftIconName={"AntDesign/caretleft"} - leftIconMode="outset" - /> + style={{ marginBottom: 20, fontFamily: "serif" }} + > + + + +
+ <> +
setValue4(value)} - /> + style={{ marginBottom: 20 }} + > + + -
+
setValue(value.toString())} />
-
+
setValue(value.toString())} />
-
+
setValue(value.toString())} - style={{ - backgroundColor: "red", - padding: 16, - }} - /> -
- -
- setValue2(value.toString())} />
@@ -128,87 +130,6 @@ function PickerExample() { />
-
- setValue(value.toString())} - style={{ - backgroundColor: "red", - borderTopWidth: 2, - borderRightWidth: 2, - borderBottomWidth: 2, - borderLeftWidth: 2, - borderColor: "green", - }} - /> -
- -
- setValue(value.toString())} - placeholderTextColor="green" - style={{ - fontSize: 30, - color: "red", - }} - leftIconName={"AntDesign/caretleft"} - /> -
- -
- setValue(value.toString())} - style={{ - paddingTop: 25, - paddingRight: 25, - paddingBottom: 25, - paddingLeft: 25, - }} - /> -
- -
- setValue(value.toString())} - style={{ - marginTop: 25, - marginRight: 25, - marginBottom: 25, - marginLeft: 25, - }} - /> -
- -
- setValue2(value.toString())} - /> -
-
element fill the height + }, + }), }, iosPickerContent: { flexDirection: "column", diff --git a/packages/core/src/components/Picker/PickerInputContainer.tsx b/packages/core/src/components/Picker/PickerInputContainer.tsx index de38c956b..7b81b851f 100644 --- a/packages/core/src/components/Picker/PickerInputContainer.tsx +++ b/packages/core/src/components/Picker/PickerInputContainer.tsx @@ -28,7 +28,6 @@ const PickerInputContainer: React.FC< onPress, Icon, style, - placeholder, selectedValue, disabled = false, zIndex, @@ -65,18 +64,21 @@ const PickerInputContainer: React.FC< return ( - - {}} - value={selectedLabel?.toString() || placeholder} - editable={false} - disabled={disabled} - style={textFieldStyle} - {...rest} - /> - + {}} + value={selectedLabel?.toString()} + editable={false} + disabled={disabled} + style={textFieldStyle} + {...rest} + /> + {children} ); diff --git a/packages/core/src/components/Picker/dropdown/MultiSelectPicker.tsx b/packages/core/src/components/Picker/dropdown/MultiSelectPicker.tsx index f4035a5a7..370a0ea98 100644 --- a/packages/core/src/components/Picker/dropdown/MultiSelectPicker.tsx +++ b/packages/core/src/components/Picker/dropdown/MultiSelectPicker.tsx @@ -7,7 +7,7 @@ import DropDownPicker from "./DropDownPicker"; import { withTheme } from "../../../theming"; const MultiSelectPicker: React.FC< - CommonDropDownPickerProps & MultiSelectPickerProps + React.PropsWithChildren > = ({ value, ...rest }) => { //@ts-ignore Ignore theme type issues return ; diff --git a/packages/core/src/components/Picker/index.tsx b/packages/core/src/components/Picker/index.tsx index 858e38614..6412fe3f5 100644 --- a/packages/core/src/components/Picker/index.tsx +++ b/packages/core/src/components/Picker/index.tsx @@ -8,7 +8,10 @@ interface PickerProps extends CommonDropDownPickerProps, SinglePickerProps { mode?: "native" | "dropdown"; } -const SinglePicker: React.FC = ({ mode = "native", ...rest }) => { +const SinglePicker: React.FC> = ({ + mode = "native", + ...rest +}) => { switch (mode) { case "native": return ; diff --git a/packages/core/src/components/TextField.tsx b/packages/core/src/components/TextField.tsx index 024b08ca7..f046663d5 100644 --- a/packages/core/src/components/TextField.tsx +++ b/packages/core/src/components/TextField.tsx @@ -238,6 +238,7 @@ class TextField extends React.Component { Icon, type = "underline", disabled = false, + editable = true, label, error = false, leftIconName, @@ -568,7 +569,8 @@ class TextField extends React.Component { ? this.state.placeholder : this.props.placeholder, placeholderTextColor: placeholderColor, - editable: !disabled, + editable: !disabled && editable, + disabled: disabled || !editable, selectionColor: activeColor, multiline, numberOfLines, From 017e5ef962bc74c576bbdf315db1348405e43fc0 Mon Sep 17 00:00:00 2001 From: Youssef Henna Date: Sun, 27 Aug 2023 19:00:04 +0300 Subject: [PATCH 13/21] Fix ios picker issue --- example/src/PickerExample.tsx | 6 ++-- .../src/components/Picker/NativePicker.tsx | 36 +++++++++---------- packages/core/src/components/Picker/index.tsx | 3 +- 3 files changed, 22 insertions(+), 23 deletions(-) diff --git a/example/src/PickerExample.tsx b/example/src/PickerExample.tsx index 7ed09d941..0e7613058 100644 --- a/example/src/PickerExample.tsx +++ b/example/src/PickerExample.tsx @@ -32,10 +32,10 @@ function PickerExample() { value={value1} mode="dropdown" onValueChange={(value) => setValue(value.toString())} - style={{ marginBottom: 20, fontFamily: "serif" }} + style={{ marginBottom: 20 }} > = ({ +const NativePicker: React.FC< + CommonPickerProps & SinglePickerProps & { theme: Theme } +> = ({ options: optionsProp = [], onValueChange, Icon, placeholder, value, autoDismissKeyboard = true, + theme, ...rest }) => { const pickerRef = React.useRef>(null); @@ -56,10 +61,7 @@ const NativePicker: React.FC = ({ onValueChange?.(""); } }} - style={[ - styles.nativePicker, - isIos ? styles.iosNativePicker : styles.nonIosPicker, - ]} + style={isIos ? styles.iosNativePicker : styles.nativePicker} onBlur={() => setPickerVisible(false)} > {options.map((option) => ( @@ -80,12 +82,10 @@ const NativePicker: React.FC = ({ + style={[styles.iosButton, { color: theme.colors.primary }]} + title="Close" + /> {renderNativePicker()} @@ -133,25 +133,23 @@ const styles = StyleSheet.create({ justifyContent: "center", width: "100%", backgroundColor: "white", + opacity: 0, ...Platform.select({ web: { height: "100%", //To have the