From e4384fc8d9561d8d77a6ca234aa35a2acbaeb6e7 Mon Sep 17 00:00:00 2001 From: Lidor Dafna <66782556+lidord-wix@users.noreply.github.com> Date: Wed, 28 Apr 2021 17:57:06 +0300 Subject: [PATCH 01/23] Infra/export SegmentedControlItemProps (#1280) --- generatedTypes/components/segmentedControl/index.d.ts | 5 +++-- generatedTypes/components/segmentedControl/segment.d.ts | 6 +++--- src/components/segmentedControl/index.tsx | 5 +++-- src/components/segmentedControl/segment.tsx | 4 ++-- src/index.ts | 2 +- 5 files changed, 12 insertions(+), 10 deletions(-) diff --git a/generatedTypes/components/segmentedControl/index.d.ts b/generatedTypes/components/segmentedControl/index.d.ts index 4189af38e4..5f270da541 100644 --- a/generatedTypes/components/segmentedControl/index.d.ts +++ b/generatedTypes/components/segmentedControl/index.d.ts @@ -1,11 +1,12 @@ import React from 'react'; import { StyleProp, ViewStyle } from 'react-native'; -import { SegmentItemProps } from './segment'; +import { SegmentedControlItemProps as SegmentProps } from './segment'; +export declare type SegmentedControlItemProps = SegmentProps; export declare type SegmentedControlProps = { /** * Array on segments. */ - segments?: SegmentItemProps[]; + segments?: SegmentedControlItemProps[]; /** * The color of the active segment label. */ diff --git a/generatedTypes/components/segmentedControl/segment.d.ts b/generatedTypes/components/segmentedControl/segment.d.ts index fa3cf40cf2..c8f063354f 100644 --- a/generatedTypes/components/segmentedControl/segment.d.ts +++ b/generatedTypes/components/segmentedControl/segment.d.ts @@ -1,6 +1,6 @@ import React from 'react'; import { LayoutChangeEvent, ImageSourcePropType, ImageStyle, StyleProp } from 'react-native'; -export declare type SegmentItemProps = { +export declare type SegmentedControlItemProps = { /** * The label of the segment. */ @@ -18,7 +18,7 @@ export declare type SegmentItemProps = { */ iconOnRight?: boolean; }; -export declare type SegmentProps = SegmentItemProps & { +export declare type SegmentProps = SegmentedControlItemProps & { /** * Is the item selected. */ @@ -44,7 +44,7 @@ export declare type SegmentProps = SegmentItemProps & { */ onLayout?: (index: number, event: LayoutChangeEvent) => void; }; -declare const _default: React.ComponentClass Date: Wed, 28 Apr 2021 18:16:25 +0300 Subject: [PATCH 02/23] export SegmentedControlItemProps in generatedTypes/index --- generatedTypes/index.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/generatedTypes/index.d.ts b/generatedTypes/index.d.ts index c7e05b92d2..faa7b48e1a 100644 --- a/generatedTypes/index.d.ts +++ b/generatedTypes/index.d.ts @@ -40,7 +40,7 @@ export {default as Image, ImageProps} from './components/image'; export {default as Overlay, OverlayTypes} from './components/overlay'; export {default as RadioButton, RadioButtonPropTypes, RadioButtonProps} from './components/radioButton/RadioButton'; export {default as RadioGroup, RadioGroupPropTypes, RadioGroupProps} from './components/radioButton/RadioGroup'; -export {default as SegmentedControl, SegmentedControlProps} from './components/segmentedControl'; +export {default as SegmentedControl, SegmentedControlProps, SegmentedControlItemProps} from './components/segmentedControl'; export {default as Switch, SwitchProps} from './components/switch'; export {default as TabController, TabControllerProps, TabControllerItemProps} from './components/tabController'; export {default as TabBar, TabBarProps} from './components/TabBar'; From e949f079fa99409b5cfc1d6d9724b204f8b8f7f8 Mon Sep 17 00:00:00 2001 From: Ethan Sharabi Date: Thu, 29 Apr 2021 14:25:06 +0300 Subject: [PATCH 03/23] Fix duplicate testID in Chip --- src/components/chip/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/chip/index.tsx b/src/components/chip/index.tsx index 3353c8a5a2..f9f6935078 100644 --- a/src/components/chip/index.tsx +++ b/src/components/chip/index.tsx @@ -194,7 +194,7 @@ const Chip = ({ tintColor={dismissColor} style={[dismissIconStyle]} accessibilityLabel="dismiss" - testID={`${testID}.dismiss`} + testID={`${testID}.dismissIcon`} /> ); From 4accb01e20914c778a0bff4977e2c8b4bf96f173 Mon Sep 17 00:00:00 2001 From: Ethan Sharabi Date: Thu, 29 Apr 2021 15:37:30 +0300 Subject: [PATCH 04/23] Fix issue with Card's backgroundColor not working (#1279) * Fix issue with Card's backgroundColor not working * Code review fixes --- src/components/card/index.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/card/index.tsx b/src/components/card/index.tsx index 1a2c230003..2f0fa1eaa3 100644 --- a/src/components/card/index.tsx +++ b/src/components/card/index.tsx @@ -194,13 +194,13 @@ class Card extends PureComponent { } } - get blurBgStyle() { - const {enableBlur} = this.props; + get backgroundStyle() { + const {enableBlur, backgroundColor = Colors.white} = this.props; if (Constants.isIOS && enableBlur) { - return {backgroundColor: Colors.rgba(Colors.white, 0.85)}; + return {backgroundColor: Colors.rgba(backgroundColor, 0.85)}; } else { - return {backgroundColor: Colors.white}; + return {backgroundColor}; } } @@ -299,7 +299,7 @@ class Card extends PureComponent { {borderRadius: brRadius}, this.elevationStyle, this.shadowStyle, - this.blurBgStyle, + this.backgroundStyle, containerStyle, style ]} From d4a21e1ad4d35a817c115cdf569e3a45558c1734 Mon Sep 17 00:00:00 2001 From: Inbal Tish <33805983+Inbal-Tish@users.noreply.github.com> Date: Sat, 1 May 2021 09:28:36 +0300 Subject: [PATCH 05/23] Infra/color picker to ts (#1235) * ColorPicker, ColorPalette, ColorSwatch and ColorPickerDialog - migrate to TS * removing unused type * types for demo screens * fix generated types * Update src/components/colorPicker/ColorPickerDialog.tsx use optional chaining instead of _.get() Co-authored-by: Ethan Sharabi * fix PR comments * removing 'height' prop passed to Dialog component * generating types * Colors.getHSL param to optional * generating types Co-authored-by: Ethan Sharabi --- ...rPickerScreen.js => ColorPickerScreen.tsx} | 38 ++-- ...rSwatchScreen.js => ColorSwatchScreen.tsx} | 53 +++-- .../components/colorPicker/ColorPalette.d.ts | 51 +++++ .../colorPicker/ColorPickerDialog.d.ts | 38 ++++ .../components/colorPicker/ColorSwatch.d.ts | 35 +++ .../components/colorPicker/index.d.ts | 43 ++++ generatedTypes/incubator/TextField/index.d.ts | 8 +- .../incubator/TextField/usePreset.d.ts | 30 +-- generatedTypes/style/colors.d.ts | 4 +- .../{ColorPalette.js => ColorPalette.tsx} | 201 +++++++++++------- ...rPickerDialog.js => ColorPickerDialog.tsx} | 164 ++++++++------ .../{ColorSwatch.js => ColorSwatch.tsx} | 125 ++++++----- .../colorPicker/{index.js => index.tsx} | 97 ++++----- src/index.ts | 5 +- src/style/colors.ts | 8 +- typings/components/ColorPicker.d.ts | 15 -- typings/components/index.d.ts | 1 - 17 files changed, 578 insertions(+), 338 deletions(-) rename demo/src/screens/componentScreens/{ColorPickerScreen.js => ColorPickerScreen.tsx} (81%) rename demo/src/screens/componentScreens/{ColorSwatchScreen.js => ColorSwatchScreen.tsx} (53%) create mode 100644 generatedTypes/components/colorPicker/ColorPalette.d.ts create mode 100644 generatedTypes/components/colorPicker/ColorPickerDialog.d.ts create mode 100644 generatedTypes/components/colorPicker/ColorSwatch.d.ts create mode 100644 generatedTypes/components/colorPicker/index.d.ts rename src/components/colorPicker/{ColorPalette.js => ColorPalette.tsx} (67%) rename src/components/colorPicker/{ColorPickerDialog.js => ColorPickerDialog.tsx} (72%) rename src/components/colorPicker/{ColorSwatch.js => ColorSwatch.tsx} (65%) rename src/components/colorPicker/{index.js => index.tsx} (68%) delete mode 100644 typings/components/ColorPicker.d.ts diff --git a/demo/src/screens/componentScreens/ColorPickerScreen.js b/demo/src/screens/componentScreens/ColorPickerScreen.tsx similarity index 81% rename from demo/src/screens/componentScreens/ColorPickerScreen.js rename to demo/src/screens/componentScreens/ColorPickerScreen.tsx index e4b0d2fb3a..168061f38c 100644 --- a/demo/src/screens/componentScreens/ColorPickerScreen.js +++ b/demo/src/screens/componentScreens/ColorPickerScreen.tsx @@ -4,6 +4,14 @@ import {StyleSheet, ScrollView} from 'react-native'; import {Colors, View, Text, ColorPicker, ColorPalette, ColorName} from 'react-native-ui-lib'; +interface Props {} +interface State { + color: string, + textColor: string, + customColors: string[], + paletteChange: boolean +} + const INITIAL_COLOR = Colors.blue30; const colors = [ '#20303C', '#43515C', '#66737C', '#858F96', '#A3ABB0', '#C2C7CB', '#E0E3E5', '#F2F4F5', @@ -17,34 +25,30 @@ const colors = [ ]; -export default class ColorPickerScreen extends Component { - constructor(props) { - super(props); - - this.state = { - color: INITIAL_COLOR, - textColor: Colors.white, - customColors: [], - paletteChange: false - }; - } +export default class ColorPickerScreen extends Component { + state: State = { + color: INITIAL_COLOR, + textColor: Colors.white, + customColors: [], + paletteChange: false + }; onDismiss = () => { console.log(`screen onDismiss`); } - onSubmit = (color, textColor) => { + onSubmit = (color: string, textColor: string) => { const {customColors} = this.state; customColors.push(color); this.setState({color, textColor, customColors: _.clone(customColors), paletteChange: false}); } - onValueChange = (value, options) => { - this.setState({color: value, textColor: options ? options.tintColor : undefined, paletteChange: false}); + onValueChange = (value: string, options: object) => { + this.setState({color: value, textColor: options ? _.get(options, 'tintColor') : undefined, paletteChange: false}); } - onPaletteValueChange = (value, options) => { - this.setState({color: value, textColor: options ? options.tintColor : undefined, paletteChange: true}); + onPaletteValueChange = (value: string, options: object) => { + this.setState({color: value, textColor: options ? _.get(options, 'tintColor') : undefined, paletteChange: true}); } render() { @@ -74,7 +78,7 @@ export default class ColorPickerScreen extends Component { Theme Color Choose a color for your place’s theme. - + Custom Colors diff --git a/demo/src/screens/componentScreens/ColorSwatchScreen.js b/demo/src/screens/componentScreens/ColorSwatchScreen.tsx similarity index 53% rename from demo/src/screens/componentScreens/ColorSwatchScreen.js rename to demo/src/screens/componentScreens/ColorSwatchScreen.tsx index 54bfb85b23..54e9658b6b 100644 --- a/demo/src/screens/componentScreens/ColorSwatchScreen.js +++ b/demo/src/screens/componentScreens/ColorSwatchScreen.tsx @@ -5,38 +5,34 @@ import {Constants, Colors, View, Text, ColorSwatch, ColorPalette} from 'react-na export default class ColorSwatchScreen extends Component { - constructor(props) { - super(props); - - this.colors = ['transparent', Colors.green30, Colors.yellow30, Colors.red30]; - this.mainColors = ['#66737C', '#459FED', '#1D5382', '#3CC7C5', '#65C888', '#FAAD4D', '#F27052', '#F2564D', '#B13DAC', '#733CA6', '#79838A', '#5847FF', '#00BBF2', '#00CD8B', '#FF563D', '#ffb600']; - this.allColors = _.filter(Object.values(Colors), _.isString); + colors = ['transparent', Colors.green30, Colors.yellow30, Colors.red30]; + mainColors = ['#66737C', '#459FED', '#1D5382', '#3CC7C5', '#65C888', '#FAAD4D', '#F27052', '#F2564D', '#B13DAC', '#733CA6', '#79838A', '#5847FF', '#00BBF2', '#00CD8B', '#FF563D', '#ffb600']; + allColors = _.filter(Object.values(Colors), _.isString); - this.state = { - color: this.colors[0], - color1: this.mainColors[this.mainColors.length - 1], - color2: this.allColors[20], - selected: false - }; - } + state = { + color: this.colors[0], + color1: this.mainColors[this.mainColors.length - 1], + color2: this.allColors[20], + selected: false + }; onPress = () => { this.setState({selected: !this.state.selected}); } - onValueChange = (value) => { + onValueChange = (value: string) => { this.setState({color: value}); } - onValueChange1 = (value) => { + onValueChange1 = (value: string) => { this.setState({color1: value}); } - onValueChange2 = (value) => { + onValueChange2 = (value: string) => { this.setState({color2: value}); } render() { const {color, color1, color2, selected} = this.state; - + return ( @@ -48,16 +44,29 @@ export default class ColorSwatchScreen extends Component { Disabled - + ColorPalette Selected Color: {color} - + Scrollable - - + + Pagination - + ); diff --git a/generatedTypes/components/colorPicker/ColorPalette.d.ts b/generatedTypes/components/colorPicker/ColorPalette.d.ts new file mode 100644 index 0000000000..34ce68d715 --- /dev/null +++ b/generatedTypes/components/colorPicker/ColorPalette.d.ts @@ -0,0 +1,51 @@ +import React from 'react'; +import { StyleProp, ViewStyle } from 'react-native'; +interface Props { + /** + * Array of colors to render in the palette + */ + colors: string[]; + /** + * Style to pass the palette container + */ + containerStyle?: StyleProp; + /** + * The container margins + */ + containerWidth?: number; + /** + * Whether to use pagination when number of colors exceeds the number of rows + */ + usePagination?: boolean; + /** + * Whether the colors pagination scrolls in a loop + */ + loop?: boolean; + /** + * The number of color rows from 2 to 5 + */ + numberOfRows?: number; + /** + * Style to pass all the ColorSwatches in the palette + */ + swatchStyle?: StyleProp; + /** + * The value of the selected swatch + */ + value?: string; + /** + * The index of the item to animate at first render (default is last) + */ + animatedIndex?: number; + /** + * Invoked once when value changes by selecting one of the swatches in the palette + */ + onValueChange?: (value: string, options: object) => void; + style?: StyleProp; + testID?: string; +} +export declare type ColorPaletteProps = Props; +declare const _default: React.ComponentClass; +export default _default; diff --git a/generatedTypes/components/colorPicker/ColorPickerDialog.d.ts b/generatedTypes/components/colorPicker/ColorPickerDialog.d.ts new file mode 100644 index 0000000000..5d827083cb --- /dev/null +++ b/generatedTypes/components/colorPicker/ColorPickerDialog.d.ts @@ -0,0 +1,38 @@ +import React from 'react'; +import { StyleProp, ViewStyle } from 'react-native'; +import { DialogProps } from '../dialog'; +interface Props extends DialogProps { + /** + * The initial color to pass the picker dialog + */ + initialColor?: string; + /** + * onSubmit callback for the picker dialog color change + */ + onSubmit?: () => void; + /** + * Props to pass the Dialog component // TODO: deprecate 'dialogProps' prop + */ + dialogProps?: object; + /** + * Additional styling for the color preview text. + */ + previewInputStyle?: StyleProp; + /** + * Accessibility labels as an object of strings, ex. {addButton: 'add custom color using hex code', dismissButton: 'dismiss', doneButton: 'done', input: 'custom hex color code'} + */ + /** + * Ok (v) button color + */ + doneButtonColor?: string; + accessibilityLabels?: { + dismissButton?: string; + doneButton?: string; + input?: string; + }; +} +export declare type ColorPickerDialogProps = Props; +declare const _default: React.ComponentClass; +export default _default; diff --git a/generatedTypes/components/colorPicker/ColorSwatch.d.ts b/generatedTypes/components/colorPicker/ColorSwatch.d.ts new file mode 100644 index 0000000000..5c005a7aa3 --- /dev/null +++ b/generatedTypes/components/colorPicker/ColorSwatch.d.ts @@ -0,0 +1,35 @@ +import React from 'react'; +import { StyleProp, ViewStyle } from 'react-native'; +interface Props { + /** + * The identifier value of the ColorSwatch in a ColorSwatch palette. + * Must be different than other ColorSwatches in the same group + */ + value?: string; + /** + * The color of the ColorSwatch + */ + color?: string; + /** + * Is the initial state is selected + */ + selected?: boolean; + /** + * Is first render should be animated + */ + animated?: boolean; + /** + * onPress callback + */ + onPress?: (value: string, options: object) => void; + index?: number; + style?: StyleProp; + testID?: string; +} +export declare type ColorSwatchProps = Props; +export declare const SWATCH_MARGIN = 12; +export declare const SWATCH_SIZE: number; +declare const _default: React.ComponentClass; +export default _default; diff --git a/generatedTypes/components/colorPicker/index.d.ts b/generatedTypes/components/colorPicker/index.d.ts new file mode 100644 index 0000000000..3571a13bd1 --- /dev/null +++ b/generatedTypes/components/colorPicker/index.d.ts @@ -0,0 +1,43 @@ +import React from 'react'; +import { StyleProp, ViewStyle } from 'react-native'; +import { ColorPickerDialogProps } from './ColorPickerDialog'; +interface Props extends ColorPickerDialogProps { + /** + * Array of colors for the picker's color palette (hex values) + */ + colors: string[]; + /** + * The value of the selected swatch // TODO: rename prop 'selectedValue' + */ + value?: string; + /** + * The index of the item to animate at first render (default is last) + */ + animatedIndex?: number; + /** + * onValueChange callback for the picker's color palette change + */ + onValueChange?: (value: string, options: object) => void; + /** + * Accessibility labels as an object of strings, ex. + * { + * addButton: 'add custom color using hex code', + * dismissButton: 'dismiss', + * doneButton: 'done', + * input: 'custom hex color code' + * } + */ + accessibilityLabels: { + addButton?: string; + dismissButton?: string; + doneButton?: string; + input?: string; + }; + style?: StyleProp; + testID?: string; +} +export declare type ColorPickerProps = Props; +declare const _default: React.ComponentClass; +export default _default; diff --git a/generatedTypes/incubator/TextField/index.d.ts b/generatedTypes/incubator/TextField/index.d.ts index 3d2d3867c5..68573566a6 100644 --- a/generatedTypes/incubator/TextField/index.d.ts +++ b/generatedTypes/incubator/TextField/index.d.ts @@ -92,7 +92,7 @@ declare const _default: React.ComponentClass<(Partial void) | import("react").RefObject | null | undefined; + ref?: import("react").RefObject | ((instance: import("react-native").TextInput | null) => void) | null | undefined; key?: string | number | null | undefined; label?: string | undefined; labelColor?: string | { @@ -200,7 +200,7 @@ export default function usePreset({ preset, ...props }: InternalTextFieldProps): charCounterStyle?: import("react-native").TextStyle | undefined; leadingAccessory?: import("react").ReactElement import("react").ReactElement | null) | (new (props: any) => import("react").Component)> | undefined; trailingAccessory?: import("react").ReactElement import("react").ReactElement | null) | (new (props: any) => import("react").Component)> | undefined; - validate?: "number" | Function | "required" | "email" | "url" | "price" | import("./types").Validator[] | undefined; + validate?: "number" | Function | "email" | "required" | "url" | "price" | import("./types").Validator[] | undefined; validateOnStart?: boolean | undefined; validateOnChange?: boolean | undefined; validateOnBlur?: boolean | undefined; @@ -403,7 +403,7 @@ export default function usePreset({ preset, ...props }: InternalTextFieldProps): disabled?: string | undefined; } | undefined; allowFontScaling?: boolean | undefined; - autoCapitalize?: "none" | "sentences" | "words" | "characters" | undefined; + autoCapitalize?: "none" | "characters" | "sentences" | "words" | undefined; autoCorrect?: boolean | undefined; autoFocus?: boolean | undefined; blurOnSubmit?: boolean | undefined; @@ -504,9 +504,9 @@ export default function usePreset({ preset, ...props }: InternalTextFieldProps): rejectResponderTermination?: boolean | null | undefined; selectionState?: import("react-native").DocumentSelectionState | undefined; spellCheck?: boolean | undefined; - textContentType?: "none" | "name" | "URL" | "addressCity" | "addressCityAndState" | "addressState" | "countryName" | "creditCardNumber" | "emailAddress" | "familyName" | "fullStreetAddress" | "givenName" | "jobTitle" | "location" | "middleName" | "namePrefix" | "nameSuffix" | "nickname" | "organizationName" | "postalCode" | "streetAddressLine1" | "streetAddressLine2" | "sublocality" | "telephoneNumber" | "username" | "password" | "newPassword" | "oneTimeCode" | undefined; + textContentType?: "none" | "name" | "password" | "username" | "URL" | "addressCity" | "addressCityAndState" | "addressState" | "countryName" | "creditCardNumber" | "emailAddress" | "familyName" | "fullStreetAddress" | "givenName" | "jobTitle" | "location" | "middleName" | "namePrefix" | "nameSuffix" | "nickname" | "organizationName" | "postalCode" | "streetAddressLine1" | "streetAddressLine2" | "sublocality" | "telephoneNumber" | "newPassword" | "oneTimeCode" | undefined; scrollEnabled?: boolean | undefined; - autoCompleteType?: "name" | "email" | "username" | "password" | "cc-csc" | "cc-exp" | "cc-exp-month" | "cc-exp-year" | "cc-number" | "postal-code" | "street-address" | "tel" | "off" | undefined; + autoCompleteType?: "name" | "off" | "cc-csc" | "cc-exp" | "cc-exp-month" | "cc-exp-year" | "cc-number" | "email" | "password" | "postal-code" | "street-address" | "tel" | "username" | undefined; importantForAutofill?: "auto" | "yes" | "no" | "noExcludeDescendants" | "yesExcludeDescendants" | undefined; disableFullscreenUI?: boolean | undefined; inlineImageLeft?: string | undefined; @@ -517,7 +517,7 @@ export default function usePreset({ preset, ...props }: InternalTextFieldProps): underlineColorAndroid?: string | typeof import("react-native").OpaqueColorValue | undefined; textAlignVertical?: "auto" | "center" | "top" | "bottom" | undefined; showSoftInputOnFocus?: boolean | undefined; - ref?: ((instance: import("react-native").TextInput | null) => void) | import("react").RefObject | null | undefined; + ref?: import("react").RefObject | ((instance: import("react-native").TextInput | null) => void) | null | undefined; key?: string | number | null | undefined; label?: string | undefined; labelColor?: string | { @@ -579,7 +579,7 @@ export default function usePreset({ preset, ...props }: InternalTextFieldProps): charCounterStyle?: import("react-native").TextStyle | undefined; leadingAccessory?: import("react").ReactElement import("react").ReactElement | null) | (new (props: any) => import("react").Component)> | undefined; trailingAccessory?: import("react").ReactElement import("react").ReactElement | null) | (new (props: any) => import("react").Component)> | undefined; - validate?: "number" | Function | "required" | "email" | "url" | "price" | import("./types").Validator[] | undefined; + validate?: "number" | Function | "email" | "required" | "url" | "price" | import("./types").Validator[] | undefined; validateOnStart?: boolean | undefined; validateOnChange?: boolean | undefined; validateOnBlur?: boolean | undefined; @@ -782,7 +782,7 @@ export default function usePreset({ preset, ...props }: InternalTextFieldProps): disabled?: string | undefined; } | undefined; allowFontScaling?: boolean | undefined; - autoCapitalize?: "none" | "sentences" | "words" | "characters" | undefined; + autoCapitalize?: "none" | "characters" | "sentences" | "words" | undefined; autoCorrect?: boolean | undefined; autoFocus?: boolean | undefined; blurOnSubmit?: boolean | undefined; @@ -995,9 +995,9 @@ export default function usePreset({ preset, ...props }: InternalTextFieldProps): rejectResponderTermination?: boolean | null | undefined; selectionState?: import("react-native").DocumentSelectionState | undefined; spellCheck?: boolean | undefined; - textContentType?: "none" | "name" | "URL" | "addressCity" | "addressCityAndState" | "addressState" | "countryName" | "creditCardNumber" | "emailAddress" | "familyName" | "fullStreetAddress" | "givenName" | "jobTitle" | "location" | "middleName" | "namePrefix" | "nameSuffix" | "nickname" | "organizationName" | "postalCode" | "streetAddressLine1" | "streetAddressLine2" | "sublocality" | "telephoneNumber" | "username" | "password" | "newPassword" | "oneTimeCode" | undefined; + textContentType?: "none" | "name" | "password" | "username" | "URL" | "addressCity" | "addressCityAndState" | "addressState" | "countryName" | "creditCardNumber" | "emailAddress" | "familyName" | "fullStreetAddress" | "givenName" | "jobTitle" | "location" | "middleName" | "namePrefix" | "nameSuffix" | "nickname" | "organizationName" | "postalCode" | "streetAddressLine1" | "streetAddressLine2" | "sublocality" | "telephoneNumber" | "newPassword" | "oneTimeCode" | undefined; scrollEnabled?: boolean | undefined; - autoCompleteType?: "name" | "email" | "username" | "password" | "cc-csc" | "cc-exp" | "cc-exp-month" | "cc-exp-year" | "cc-number" | "postal-code" | "street-address" | "tel" | "off" | undefined; + autoCompleteType?: "name" | "off" | "cc-csc" | "cc-exp" | "cc-exp-month" | "cc-exp-year" | "cc-number" | "email" | "password" | "postal-code" | "street-address" | "tel" | "username" | undefined; importantForAutofill?: "auto" | "yes" | "no" | "noExcludeDescendants" | "yesExcludeDescendants" | undefined; disableFullscreenUI?: boolean | undefined; inlineImageLeft?: string | undefined; @@ -1008,7 +1008,7 @@ export default function usePreset({ preset, ...props }: InternalTextFieldProps): underlineColorAndroid?: string | typeof import("react-native").OpaqueColorValue | undefined; textAlignVertical?: "auto" | "center" | "top" | "bottom" | undefined; showSoftInputOnFocus?: boolean | undefined; - ref?: ((instance: import("react-native").TextInput | null) => void) | import("react").RefObject | null | undefined; + ref?: import("react").RefObject | ((instance: import("react-native").TextInput | null) => void) | null | undefined; key?: string | number | null | undefined; label?: string | undefined; labelColor: string | { @@ -1078,7 +1078,7 @@ export default function usePreset({ preset, ...props }: InternalTextFieldProps): charCounterStyle?: import("react-native").TextStyle | undefined; leadingAccessory?: import("react").ReactElement import("react").ReactElement | null) | (new (props: any) => import("react").Component)> | undefined; trailingAccessory?: import("react").ReactElement import("react").ReactElement | null) | (new (props: any) => import("react").Component)> | undefined; - validate?: "number" | Function | "required" | "email" | "url" | "price" | import("./types").Validator[] | undefined; + validate?: "number" | Function | "email" | "required" | "url" | "price" | import("./types").Validator[] | undefined; validateOnStart?: boolean | undefined; validateOnChange?: boolean | undefined; validateOnBlur: boolean; diff --git a/generatedTypes/style/colors.d.ts b/generatedTypes/style/colors.d.ts index 8c806fa65d..0a87f3235a 100644 --- a/generatedTypes/style/colors.d.ts +++ b/generatedTypes/style/colors.d.ts @@ -45,8 +45,8 @@ export declare class Colors { isDark(color: string): boolean; isValidHex(string: string): boolean; getHexString(color: string): string; - getHSL(color: string): tinycolor.ColorFormats.HSLA; - isTransparent(color: string): boolean; + getHSL(color?: string): tinycolor.ColorFormats.HSLA; + isTransparent(color?: string): boolean | "" | undefined; areEqual(colorA: string, colorB: string): boolean; } declare const colorObject: Colors & { diff --git a/src/components/colorPicker/ColorPalette.js b/src/components/colorPicker/ColorPalette.tsx similarity index 67% rename from src/components/colorPicker/ColorPalette.js rename to src/components/colorPicker/ColorPalette.tsx index 0a3f3bb84c..7b3504c44a 100644 --- a/src/components/colorPicker/ColorPalette.js +++ b/src/components/colorPicker/ColorPalette.tsx @@ -1,17 +1,70 @@ import _ from 'lodash'; -import PropTypes from 'prop-types'; import memoize from 'memoize-one'; -import React from 'react'; -import {StyleSheet, UIManager, findNodeHandle} from 'react-native'; +import React, {PureComponent} from 'react'; +import {StyleSheet, UIManager, findNodeHandle, StyleProp, ViewStyle} from 'react-native'; import {Constants} from '../../helpers'; import {Colors} from '../../style'; -import {PureBaseComponent} from '../../commons'; +import {asBaseComponent} from '../../commons/new'; import View from '../view'; import Carousel from '../carousel'; import PageControl from '../pageControl'; import ColorSwatch, {SWATCH_SIZE} from './ColorSwatch'; import ScrollBar from '../scrollBar'; + +interface Props { + /** + * Array of colors to render in the palette + */ + colors: string[]; + /** + * Style to pass the palette container + */ + containerStyle?: StyleProp; + /** + * The container margins + */ + containerWidth?: number; + /** + * Whether to use pagination when number of colors exceeds the number of rows + */ + usePagination?: boolean; + /** + * Whether the colors pagination scrolls in a loop + */ + loop?: boolean; + /** + * The number of color rows from 2 to 5 + */ + numberOfRows?: number; + /** + * Style to pass all the ColorSwatches in the palette + */ + swatchStyle?: StyleProp; + /** + * The value of the selected swatch + */ + value?: string; + /** + * The index of the item to animate at first render (default is last) + */ + animatedIndex?: number; + /** + * Invoked once when value changes by selecting one of the swatches in the palette + */ + onValueChange?: (value: string, options: object) => void; + style?: StyleProp; + testID?: string; +} +export type ColorPaletteProps = Props; + +interface State { + currentPage: number, + scrollable: boolean, + orientation?: string, + contentWidth?: number +} + const VERTICAL_PADDING = 16; const HORIZONTAL_PADDING = 20; const MINIMUM_MARGIN = 16; @@ -23,71 +76,40 @@ const DEFAULT_NUMBER_OF_ROWS = 3; * @example: https://github.com/wix/react-native-ui-lib/blob/master/demo/src/screens/componentScreens/ColorPickerScreen.js * @notes: This is a screen width component */ -export default class ColorPalette extends PureBaseComponent { +class ColorPalette extends PureComponent { static displayName = 'ColorPalette'; - static propTypes = { - /** - * Array of colors to render in the palette - */ - colors: PropTypes.arrayOf(PropTypes.string).isRequired, - /** - * Style to pass the palette container - */ - containerStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.array]), - /** - * The container margins - */ - containerWidth: PropTypes.number, - /** - * Whether to use pagination when number of colors exceeds the number of rows - */ - usePagination: PropTypes.bool, - /** - * Whether the colors pagination scrolls in a loop - */ - loop: PropTypes.bool, - /** - * The number of color rows from 2 to 5 - */ - numberOfRows: PropTypes.number, - /** - * Style to pass all the ColorSwatches in the palette - */ - swatchStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.array]), - /** - * The value of the selected swatch - */ - value: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]), - /** - * The index of the item to animate at first render (default is last) - */ - animatedIndex: PropTypes.number, - /** - * Invoked once when value changes by selecting one of the swatches in the palette - */ - onValueChange: PropTypes.func - }; - static defaultProps = { numberOfRows: DEFAULT_NUMBER_OF_ROWS, usePagination: true, loop: true }; - constructor(props) { + constructor(props: Props) { super(props); this.state = { currentPage: 0, - scrollable: false + scrollable: false, + orientation: undefined, + contentWidth: undefined }; - this.carousel = React.createRef(); - this.scrollBar = React.createRef(); this.initLocalVariables(); } + carousel: React.RefObject = React.createRef(); + scrollBar: React.RefObject = React.createRef(); + itemsRefs?: React.RefObject[] = undefined; + selectedColorIndex?: number = undefined; + selectedPage?: number = undefined; + currentColorsCount?: number = undefined; + itemsPerRow = 0; + itemsPerPage = 0; + usePagination?: boolean = undefined; + innerMargin?: number = undefined; + swatchStyles?: StyleProp[] = undefined; + componentDidMount() { Constants.addDimensionsEventListener(this.onOrientationChanged); } @@ -112,7 +134,7 @@ export default class ColorPalette extends PureBaseComponent { this.itemsPerPage = this.itemsPerRow * this.getNumberOfRows(); this.usePagination = this.shouldUsePagination(); this.innerMargin = this.getInnerMargin(); - this.swatchStyle = []; + this.swatchStyles = []; } get value() { @@ -129,7 +151,7 @@ export default class ColorPalette extends PureBaseComponent { } get containerWidth() { - const {containerWidth} = this.getThemeProps(); + const {containerWidth} = this.props; return containerWidth || Constants.screenWidth; } @@ -144,10 +166,11 @@ export default class ColorPalette extends PureBaseComponent { }); getNumberOfRows() { - const {numberOfRows} = this.props; + const {numberOfRows = DEFAULT_NUMBER_OF_ROWS} = this.props; if (!_.inRange(numberOfRows, 2, 6)) { - console.warn(`${numberOfRows} is not within valid range of color rows (2 to 5); defaulting to ${DEFAULT_NUMBER_OF_ROWS}.`); + console.warn(`${numberOfRows} is not within valid range of color rows (2 to 5); + defaulting to ${DEFAULT_NUMBER_OF_ROWS}.`); return DEFAULT_NUMBER_OF_ROWS; } return numberOfRows; @@ -187,41 +210,46 @@ export default class ColorPalette extends PureBaseComponent { scrollToSelected() { const {scrollable, currentPage} = this.state; - if (scrollable && this.selectedColorIndex !== undefined) { + if (scrollable && this.selectedColorIndex !== undefined && this.itemsRefs) { const childRef = this.itemsRefs[this.selectedColorIndex]; if (childRef) { - const handle = findNodeHandle(childRef); + const handle = findNodeHandle(childRef.current); if (handle) { - UIManager.measureLayoutRelativeToParent(handle, - e => { - console.warn(e); - }, - (x, _y, w, _h) => { - if (x + w > this.containerWidth) { + //@ts-ignore + UIManager.measureLayoutRelativeToParent(handle, e => { + console.warn(e); + }, + (x: number, _y: number, w: number, _h: number) => { + if (x + w > this.containerWidth) { + if (this.scrollBar && this.scrollBar.current) { this.scrollBar.current.scrollTo({ x: x + w + HORIZONTAL_PADDING - this.containerWidth, y: 0, animated: false }); } - }); + } + }); } } } else if (this.usePagination) { - this.carousel.current.goToPage(this.selectedPage || currentPage, false); + if (this.carousel && this.carousel.current) { + this.carousel.current.goToPage(this.selectedPage || currentPage, false); + } } } - onContentSizeChange = contentWidth => { - this.setState({scrollable: contentWidth > this.containerWidth, contentWidth}); + onContentSizeChange = (contentWidth: number) => { + this.setState({ + scrollable: contentWidth > this.containerWidth, contentWidth}); }; - onChangePage = index => { + onChangePage = (index: number) => { this.setState({currentPage: index}); }; - onValueChange = (value, options) => { + onValueChange = (value: string, options: object) => { _.invoke(this.props, 'onValueChange', value, options); }; @@ -231,7 +259,7 @@ export default class ColorPalette extends PureBaseComponent { }, 0); }; - getHorizontalMargins = index => { + getHorizontalMargins = (index: number) => { const isFirst = index === 0; const isOnLeft = isFirst || index % this.itemsPerRow === 0; const isOnRight = index % this.itemsPerRow === this.itemsPerRow - 1; @@ -251,22 +279,29 @@ export default class ColorPalette extends PureBaseComponent { return {marginLeft, marginRight}; }; - getSwatchStyle = index => { + getSwatchStyle = (index: number) => { const sizeHasChanged = this.colors.length !== this.currentColorsCount; const isNextToLastIndex = index === this.colors.length - 2; // Need to update the next to last item because it's margin needs to changed - if (_.isUndefined(this.swatchStyle[index]) || (!this.usePagination && sizeHasChanged && isNextToLastIndex)) { - this.swatchStyle[index] = {...this.getHorizontalMargins(index), ...this.props.swatchStyle}; - if (sizeHasChanged && isNextToLastIndex) { - this.currentColorsCount = this.colors.length; + if (!_.isUndefined(this.swatchStyles)) { + if (_.isUndefined(this.swatchStyles[index]) || (!this.usePagination && sizeHasChanged && isNextToLastIndex)) { + this.swatchStyles[index] = [this.getHorizontalMargins(index), this.props.swatchStyle]; + if (sizeHasChanged && isNextToLastIndex) { + this.currentColorsCount = this.colors.length; + } } + return this.swatchStyles[index]; } - - return this.swatchStyle[index]; }; - renderColorSwatch(color, index) { + addRefByIndex = (index: number, ref?: any) => { + if (this.itemsRefs && ref) { + this.itemsRefs[index] = ref; + } + } + + renderColorSwatch(color: string, index: number) { const {animatedIndex, testID} = this.props; return ( @@ -279,13 +314,13 @@ export default class ColorPalette extends PureBaseComponent { selected={this.value === color} animated={index === animatedIndex} onPress={this.onValueChange} - ref={r => (this.itemsRefs[index] = r)} + ref={r => this.addRefByIndex(index, r)} testID={`${testID}-${color}`} /> ); } - renderPalette(props, contentStyle, colors, pageIndex) { + renderPalette(props: Props, contentStyle: StyleProp, colors: string[], pageIndex: number) { const {style, ...others} = props; this.itemsRefs = []; @@ -316,7 +351,7 @@ export default class ColorPalette extends PureBaseComponent { containerProps={{width: !scrollable ? contentWidth : undefined}} gradientHeight={SCROLLABLE_HEIGHT - 12} > - {this.renderPalette(others, styles.scrollContent, this.colors)} + {this.renderPalette(others, styles.scrollContent, this.colors, 0)} ); } @@ -350,6 +385,8 @@ export default class ColorPalette extends PureBaseComponent { } } +export default asBaseComponent(ColorPalette); + const styles = StyleSheet.create({ paletteContainer: { flexDirection: 'row', diff --git a/src/components/colorPicker/ColorPickerDialog.js b/src/components/colorPicker/ColorPickerDialog.tsx similarity index 72% rename from src/components/colorPicker/ColorPickerDialog.js rename to src/components/colorPicker/ColorPickerDialog.tsx index 9373d839a7..8a3eed9145 100644 --- a/src/components/colorPicker/ColorPickerDialog.js +++ b/src/components/colorPicker/ColorPickerDialog.tsx @@ -1,19 +1,67 @@ import _ from 'lodash'; -import PropTypes from 'prop-types'; import React, {PureComponent} from 'react'; -import {LayoutAnimation, StyleSheet, Keyboard, TextInput, PixelRatio, I18nManager} from 'react-native'; +import { + LayoutAnimation, + StyleSheet, + Keyboard, + TextInput, + PixelRatio, + I18nManager, + StyleProp, + ViewStyle, + EmitterSubscription +} from 'react-native'; import {Constants} from '../../helpers'; -import {asBaseComponent} from '../../commons'; +import {asBaseComponent} from '../../commons/new'; import Assets from '../../assets'; import {Colors, Typography} from '../../style'; import View from '../view'; import Text from '../text'; import TouchableOpacity from '../touchableOpacity'; -import Dialog from '../dialog'; +import Dialog, {DialogProps} from '../dialog'; import Button from '../button'; +//@ts-expect-error import ColorSliderGroup from '../slider/ColorSliderGroup'; import PanningProvider from '../panningViews/panningProvider'; +interface Props extends DialogProps { + /** + * The initial color to pass the picker dialog + */ + initialColor?: string; + /** + * onSubmit callback for the picker dialog color change + */ + onSubmit?: () => void; + /** + * Props to pass the Dialog component // TODO: deprecate 'dialogProps' prop + */ + dialogProps?: object; + /** + * Additional styling for the color preview text. + */ + previewInputStyle?: StyleProp; + /** + * Accessibility labels as an object of strings, ex. {addButton: 'add custom color using hex code', dismissButton: 'dismiss', doneButton: 'done', input: 'custom hex color code'} + */ + /** + * Ok (v) button color + */ + doneButtonColor?: string, + accessibilityLabels?: { + dismissButton?: string, + doneButton?: string, + input?: string + }; +} +export type ColorPickerDialogProps = Props; + +interface State { + keyboardHeight: number, + color: any, + text?: string, + valid: boolean +} const KEYBOARD_HEIGHT = 216; @@ -22,42 +70,14 @@ const KEYBOARD_HEIGHT = 216; * @extends: Dialog * @example: https://github.com/wix/react-native-ui-lib/blob/master/demo/src/screens/componentScreens/ColorPickerScreen.js */ -class ColorPickerDialog extends PureComponent { - static displayName = 'ColorPickerDialog'; - - static propTypes = { - ...Dialog.PropTypes, - /** - * The initial color to pass the picker dialog - */ - initialColor: PropTypes.string, - /** - * onSubmit callback for the picker dialog color change - */ - onSubmit: PropTypes.func, - /** - * Props to pass the Dialog component // TODO: deprecate 'dialogProps' prop - */ - dialogProps: PropTypes.object, - /** - * Additional styling for the color preview text. - */ - previewInputStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.array]), - /** - * Accessibility labels as an object of strings, ex. {addButton: 'add custom color using hex code', dismissButton: 'dismiss', doneButton: 'done', input: 'custom hex color code'} - */ - accessibilityLabels: PropTypes.shape({ - dismissButton: PropTypes.string, - doneButton: PropTypes.string, - input: PropTypes.string - }) - }; +class ColorPickerDialog extends PureComponent { + static displayName = 'ColorPicker'; static defaultProps = { initialColor: Colors.dark80 }; - constructor(props) { + constructor(props: Props) { super(props); const color = Colors.getHSL(props.initialColor); @@ -72,6 +92,12 @@ class ColorPickerDialog extends PureComponent { }; } + textInput: React.RefObject = React.createRef(); + //@ts-ignore + private keyboardDidShowListener: EmitterSubscription; + //@ts-ignore + private keyboardDidHideListener: EmitterSubscription; + componentDidMount() { this.keyboardDidShowListener = Keyboard.addListener('keyboardDidShow', this.keyboardDidShow); this.keyboardDidHideListener = Keyboard.addListener('keyboardDidHide', this.keyboardDidHide); @@ -82,7 +108,7 @@ class ColorPickerDialog extends PureComponent { this.keyboardDidHideListener.remove(); } - keyboardDidShow = e => { + keyboardDidShow = (e: any) => { if (Constants.isIOS && this.state.keyboardHeight !== e.endCoordinates.height) { this.setState({keyboardHeight: e.endCoordinates.height}); } @@ -99,27 +125,25 @@ class ColorPickerDialog extends PureComponent { }; setFocus = () => { - if (this.textInput) { - this.textInput.focus(); - } + this.textInput?.current?.focus(); }; - changeHeight(height) { + changeHeight(height: number) { if (Constants.isAndroid && this.state.keyboardHeight !== height) { LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut); this.setState({keyboardHeight: height}); } } - getColorValue(color) { - if (!_.isString(color)) { + getColorValue(color?: string) { + if (!color) { return; } return color.replace('#', ''); } - getHexColor(text) { - if (text && !Colors.isTransparent(text)) { + getHexColor(text: string) { + if (!Colors.isTransparent(text)) { const trimmed = text.replace(/\s+/g, ''); const hex = `#${trimmed}`; return hex; @@ -127,24 +151,26 @@ class ColorPickerDialog extends PureComponent { return text; } - getHexString(color) { + getHexString(color: string) { return _.toUpper(Colors.getHexString(color)); } - getTextColor(color) { + getTextColor(color: string) { return Colors.isDark(color) ? Colors.white : Colors.dark10; } - getValidColorString(text) { - const hex = this.getHexColor(text); + getValidColorString(text?: string) { + if (text) { + const hex = this.getHexColor(text); - if (Colors.isValidHex(hex)) { - return {hex, valid: true}; + if (Colors.isValidHex(hex)) { + return {hex, valid: true}; + } } return {undefined, valid: false}; } - applyColor = (text) => { + applyColor = (text: string) => { const {hex, valid} = this.getValidColorString(text); if (hex) { @@ -154,15 +180,16 @@ class ColorPickerDialog extends PureComponent { } }; - updateColor(color) { + updateColor(color: string) { const hex = this.getHexString(color); const text = this.getColorValue(hex); this.setState({color, text, valid: true}); } resetValues() { - const color = Colors.getHSL(this.props.initialColor); - const text = this.getColorValue(this.props.initialColor); + const {initialColor} = this.props; + const color = Colors.getHSL(initialColor); + const text = this.getColorValue(initialColor); const {valid} = this.getValidColorString(text); this.setState({ @@ -172,12 +199,11 @@ class ColorPickerDialog extends PureComponent { }); } - onSliderValueChange = color => { - const c = Colors.getHSL(color); - this.updateColor(c); + onSliderValueChange = (color: string) => { + this.updateColor(color); }; - onChangeText = value => { + onChangeText = (value: string) => { this.applyColor(value); }; @@ -197,7 +223,7 @@ class ColorPickerDialog extends PureComponent { }; renderHeader() { - const {useCustomTheme, accessibilityLabels} = this.props; + const {doneButtonColor, accessibilityLabels} = this.props; const {valid} = this.state; return ( @@ -207,15 +233,15 @@ class ColorPickerDialog extends PureComponent { iconSource={Assets.icons.x} iconStyle={{tintColor: Colors.dark10}} onPress={this.onDismiss} - accessibilityLabel={accessibilityLabels.dismissButton} + accessibilityLabel={_.get(accessibilityLabels, 'dismissButton')} />