diff --git a/assets/icons/lock.svg b/assets/icons/lock.svg new file mode 100644 index 0000000000..bfce8aadd3 --- /dev/null +++ b/assets/icons/lock.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/components/inputs/SelectInput.tsx b/packages/components/inputs/SelectInput.tsx new file mode 100644 index 0000000000..ae7d06b378 --- /dev/null +++ b/packages/components/inputs/SelectInput.tsx @@ -0,0 +1,232 @@ +import React, { ReactElement, useState } from "react"; +import { + View, + ScrollView, + StyleSheet, + StyleProp, + ViewStyle, +} from "react-native"; + +import { Label } from "./TextInputCustom"; +import chevronDownSVG from "../../../assets/icons/chevron-down.svg"; +import chevronUpSVG from "../../../assets/icons/chevron-up.svg"; +import lockSVG from "../../../assets/icons/lock.svg"; +import { + neutral00, + neutral33, + neutral77, + neutralA3, + secondaryColor, +} from "../../utils/style/colors"; +import { fontMedium13, fontSemibold14 } from "../../utils/style/fonts"; +import { layout } from "../../utils/style/layout"; +import { BrandText } from "../BrandText"; +import { SVG } from "../SVG"; +import { CustomPressable } from "../buttons/CustomPressable"; +import { SpacerColumn, SpacerRow } from "../spacer"; + +export type SelectInputDataValue = string | number; + +export type SelectInputData = { + label: string; + value: SelectInputDataValue; + iconComponent?: ReactElement; +}; + +type Props = { + data: SelectInputData[]; + placeHolder?: string; + selectedData: SelectInputData; + setData: (data: SelectInputData) => void; + disabled?: boolean; + style?: StyleProp; + boxStyle?: StyleProp; + label?: string; + isRequired?: boolean; +}; + +export const SelectInput: React.FC = ({ + data, + placeHolder, + selectedData, + setData, + disabled, + style, + boxStyle, + label, + isRequired, +}) => { + const [openMenu, setOpenMenu] = useState(false); + const [hoveredIndex, setHoveredIndex] = useState(0); + const [hovered, setHovered] = useState(false); + + const getScrollViewStyle = () => { + if (data.length > 5) { + return [styles.dropdownMenu, { height: 200 }]; + } + return styles.dropdownMenu; + }; + + return ( + setHovered(true)} + onHoverOut={() => setHovered(false)} + onPress={() => { + if (!disabled || data.length) setOpenMenu((value) => !value); + }} + disabled={disabled || !data.length} + > + {label && ( + <> + + + + )} + + + + {selectedData.iconComponent && ( + <> + {selectedData.iconComponent} + + + )} + + + {selectedData?.label ? selectedData.label : placeHolder} + + + + + + + {/*TODO: If the opened menu appears under other elements, you'll may need to set zIndex:-1 or something to these elements*/} + {openMenu && ( + + {data.map((item, index) => ( + { + setHoveredIndex(index + 1); + setHovered(true); + }} + onHoverOut={() => { + setHoveredIndex(0); + setHovered(false); + }} + onPress={() => { + setData(item); + setOpenMenu(false); + }} + key={index} + style={styles.dropdownMenuRow} + > + + {item.iconComponent && ( + <> + {item.iconComponent} + + + )} + + + {item.label} + + + + ))} + + )} + + + ); +}; + +const styles = StyleSheet.create({ + selectInputLabel: StyleSheet.flatten([fontSemibold14, { color: neutralA3 }]), + selectInput: { + backgroundColor: neutral00, + fontSize: 14, + fontWeight: 600, + color: secondaryColor, + borderColor: neutral33, + borderWidth: 1, + borderRadius: 12, + padding: layout.padding_x1_5, + flexDirection: "row", + justifyContent: "space-between", + alignItems: "center", + }, + inputContainer: { + backgroundColor: neutral00, + borderWidth: 1, + borderColor: neutral33, + borderRadius: 12, + paddingHorizontal: layout.padding_x1_5, + }, + inputItemStyle: { + backgroundColor: "#292929", + color: neutralA3, + paddingVertical: layout.padding_x1_5, + paddingHorizontal: layout.padding_x1, + }, + iconLabel: { + flexDirection: "row", + alignItems: "center", + }, + + dropdownMenu: { + backgroundColor: "#292929", + borderWidth: 1, + borderColor: neutral33, + borderRadius: 12, + padding: layout.padding_x1, + position: "absolute", + top: 52, + width: "100%", + zIndex: 10, + }, + dropdownMenuText: StyleSheet.flatten([fontMedium13]), + dropdownMenuRow: { + borderRadius: 6, + padding: layout.padding_x1, + }, + // dropdownMenuRow: { + // backgroundColor: neutral00, + // borderRadius: 6, + // padding: layout.padding_x1, + // }, +}); diff --git a/packages/components/inputs/TextInputCustom.tsx b/packages/components/inputs/TextInputCustom.tsx index 84a9681655..9f91a19b8f 100644 --- a/packages/components/inputs/TextInputCustom.tsx +++ b/packages/components/inputs/TextInputCustom.tsx @@ -6,6 +6,7 @@ import React, { useEffect, useMemo, useRef, + useState, } from "react"; import { RegisterOptions, @@ -16,7 +17,7 @@ import { FieldValues, } from "react-hook-form"; import { - Pressable, + ActivityIndicator, StyleProp, StyleSheet, TextInput, @@ -27,7 +28,6 @@ import { } from "react-native"; import { SvgProps } from "react-native-svg"; -// import { TextInputLabelProps } from "./TextInputOutsideLabel"; import { DEFAULT_FORM_ERRORS } from "../../utils/errors"; import { handleKeyPress } from "../../utils/keyboard"; import { @@ -39,22 +39,19 @@ import { neutralA3, secondaryColor, } from "../../utils/style/colors"; -import { - fontMedium10, - fontSemibold14, - fontSemibold20, -} from "../../utils/style/fonts"; +import { fontMedium10, fontSemibold14 } from "../../utils/style/fonts"; import { layout } from "../../utils/style/layout"; import { BrandText } from "../BrandText"; import { ErrorText } from "../ErrorText"; import { SVG } from "../SVG"; import { TertiaryBox } from "../boxes/TertiaryBox"; +import { CustomPressable } from "../buttons/CustomPressable"; import { SpacerColumn, SpacerRow } from "../spacer"; export interface TextInputCustomProps extends Omit { label: string; - variant?: "regular" | "labelOutside" | "noCropBorder" | "noStyle"; + variant?: "regular" | "labelOutside" | "noStyle"; iconSVG?: React.FC; placeHolder?: string; squaresBackgroundColor?: string; @@ -72,7 +69,9 @@ export interface TextInputCustomProps defaultValue?: PathValue>; subtitle?: React.ReactElement; hideLabel?: boolean; + errorStyle?: ViewStyle; valueModifier?: (value: string) => string; + isLoading?: boolean; labelStyle?: TextStyle; containerStyle?: ViewStyle; boxMainContainerStyle?: ViewStyle; @@ -84,21 +83,30 @@ export interface TextInputCustomProps export const Label: React.FC<{ children: string; - style?: TextStyle; + style?: StyleProp; isRequired?: boolean; -}> = ({ children, style, isRequired }) => ( + hovered?: boolean; +}> = ({ children, style, isRequired, hovered }) => ( - + {children} {!!isRequired && children && ( @@ -123,6 +131,7 @@ export const TextInputCustom = ({ width, height, variant = "regular", + noBrokenCorners, name, control, defaultValue, @@ -130,10 +139,10 @@ export const TextInputCustom = ({ subtitle, labelStyle, iconSVG, - noBrokenCorners, - // isAsterickSign, hideLabel, valueModifier, + errorStyle, + isLoading, containerStyle, boxMainContainerStyle, error, @@ -141,14 +150,13 @@ export const TextInputCustom = ({ setRef, ...restProps }: TextInputCustomProps) => { - // variables const { field, fieldState } = useController({ name, control, rules, - defaultValue, }); const inputRef = useRef(null); + const [hovered, setHovered] = useState(false); // Passing ref to parent since I didn't find a pattern to handle generic argument AND forwardRef useEffect(() => { if (inputRef.current && setRef) { @@ -158,7 +166,7 @@ export const TextInputCustom = ({ useEffect(() => { if (defaultValue) { - handleChangeText(defaultValue); + handleChangeText(defaultValue || ""); } // handleChangeText changes on every render and we want to call handleChangeText only when default value changes so we disable exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps @@ -218,25 +226,35 @@ export const TextInputCustom = ({ ); return ( - - {variant === "labelOutside" && ( + setHovered(true)} + onHoverOut={() => setHovered(false)} + onPress={() => inputRef?.current?.focus()} + disabled={disabled} + > + {variant === "labelOutside" && !hideLabel && ( <> - - + )} - ({ )} {!variant || - (!["labelOutside", "noCropBorder"].includes(variant) && - !hideLabel && ( - inputRef.current?.focus()}> - - {label} - - - - ))} + (variant !== "labelOutside" && !hideLabel && ( + <> + + {label} + + + + ))} handleKeyPress({ event, onPressEnter })} - placeholderTextColor={neutralA3} + placeholderTextColor={neutral77} value={field.value} style={[styles.textInput, textInputStyle]} {...restProps} /> - <>{children} + {isLoading ? ( + + ) : ( + <>{children} + )} {error || fieldError} - + ); }; const styles = StyleSheet.create({ - rowEnd: { - flexDirection: "row", - justifyContent: "space-between", - alignItems: "flex-end", - }, mainContainer: { alignItems: "flex-start", paddingHorizontal: 12, @@ -304,7 +320,7 @@ const styles = StyleSheet.create({ paddingVertical: layout.padding_x1_5, }, labelText: { - color: neutral77, + color: neutralA3, }, textInput: { fontSize: 14, @@ -317,4 +333,9 @@ const styles = StyleSheet.create({ alignItems: "center", width: "100%", }, + rowEnd: { + flexDirection: "row", + justifyContent: "space-between", + alignItems: "flex-end", + }, }); diff --git a/packages/screens/FeedNewArticle/FeedNewArticleScreen.tsx b/packages/screens/FeedNewArticle/FeedNewArticleScreen.tsx index e1f5a4e846..d6ce7073c7 100644 --- a/packages/screens/FeedNewArticle/FeedNewArticleScreen.tsx +++ b/packages/screens/FeedNewArticle/FeedNewArticleScreen.tsx @@ -268,31 +268,32 @@ export const FeedNewArticleScreen: ScreenFC<"FeedNewArticle"> = () => { borderRadius: 12, }} /> - - - {/**@ts-ignore error:TS2589: Type instantiation is excessively deep and possibly infinite. */} - ( - - )} - /> + + + + ( + + )} + /> + ); diff --git a/packages/screens/Organizations/components/ConfigureVotingSection.tsx b/packages/screens/Organizations/components/ConfigureVotingSection.tsx index 1ab0124e5d..57f8caa5f9 100644 --- a/packages/screens/Organizations/components/ConfigureVotingSection.tsx +++ b/packages/screens/Organizations/components/ConfigureVotingSection.tsx @@ -76,7 +76,7 @@ export const ConfigureVotingSection: React.FC = ({ name="days" - variant="noCropBorder" + noBrokenCorners hideLabel control={control} label="" @@ -89,7 +89,7 @@ export const ConfigureVotingSection: React.FC = ({ name="hours" - variant="noCropBorder" + noBrokenCorners hideLabel control={control} label="" @@ -103,7 +103,7 @@ export const ConfigureVotingSection: React.FC = ({ name="minutes" - variant="noCropBorder" + noBrokenCorners hideLabel control={control} label="" diff --git a/packages/screens/Organizations/components/CreateDAOSection.tsx b/packages/screens/Organizations/components/CreateDAOSection.tsx index 6871ce06fb..7e48beecf2 100644 --- a/packages/screens/Organizations/components/CreateDAOSection.tsx +++ b/packages/screens/Organizations/components/CreateDAOSection.tsx @@ -63,43 +63,44 @@ export const CreateDAOSection: React.FC = ({ + noBrokenCorners + variant="labelOutside" control={control} - variant="noCropBorder" label="Organization's name" placeHolder="Type organization's name here" name="organizationName" rules={{ required: true }} - // isAsterickSign /> + noBrokenCorners + variant="labelOutside" control={control} - variant="noCropBorder" label="Associated Teritori Name Service" placeHolder="your-organization.tori" name="associatedTeritoriNameService" rules={{ required: true }} - // isAsterickSign /> + noBrokenCorners control={control} - variant="noCropBorder" + variant="labelOutside" label="Organization's image url" placeHolder="https://example.com/preview.png" name="imageUrl" rules={{ required: true }} - // isAsterickSign /> + noBrokenCorners + variant="labelOutside" control={control} - variant="noCropBorder" label="Organization's description" placeHolder="Type organization's description here" name="organizationDescription" diff --git a/packages/screens/Organizations/components/MemberSettingsSection.tsx b/packages/screens/Organizations/components/MemberSettingsSection.tsx index c514cc3513..d61fbb698f 100644 --- a/packages/screens/Organizations/components/MemberSettingsSection.tsx +++ b/packages/screens/Organizations/components/MemberSettingsSection.tsx @@ -54,11 +54,10 @@ export const MemberSettingsSection: React.FC = ({ name={`members.${index}.addr`} - variant="noCropBorder" + noBrokenCorners label="Member Address" hideLabel={index > 0} control={control} - // isAsterickSign rules={{ required: true, validate: validateAddress }} placeHolder="Account address" iconSVG={walletInputSVG} @@ -75,11 +74,10 @@ export const MemberSettingsSection: React.FC = ({ name={`members.${index}.weight`} - variant="noCropBorder" + noBrokenCorners label="Weight" hideLabel={index > 0} control={control} - // isAsterickSign rules={{ required: true, pattern: patternOnlyNumbers }} placeHolder="1" /> diff --git a/packages/screens/Organizations/components/TokenSettingsSection.tsx b/packages/screens/Organizations/components/TokenSettingsSection.tsx index 0dfe358e59..970ae4581d 100644 --- a/packages/screens/Organizations/components/TokenSettingsSection.tsx +++ b/packages/screens/Organizations/components/TokenSettingsSection.tsx @@ -56,10 +56,9 @@ export const TokenSettingsSection: React.FC = ({ name="tokenName" - variant="noCropBorder" + noBrokenCorners label="Token name" control={control} - // isAsterickSign rules={{ required: true }} placeHolder="My Organization Token" /> @@ -68,10 +67,9 @@ export const TokenSettingsSection: React.FC = ({ name="tokenSymbol" - variant="noCropBorder" + noBrokenCorners label="Token Symbol" control={control} - // isAsterickSign valueModifier={(value) => value.toUpperCase()} rules={{ required: true, pattern: patternOnlyLetters }} placeHolder="ABC" @@ -85,11 +83,10 @@ export const TokenSettingsSection: React.FC = ({ name={`tokenHolders.${index}.address`} - variant="noCropBorder" + noBrokenCorners label="Token Holders" hideLabel={index > 0} control={control} - // isAsterickSign rules={{ required: true, validate: validateAddress }} placeHolder="Account address" iconSVG={walletInputSVG} @@ -106,11 +103,10 @@ export const TokenSettingsSection: React.FC = ({ name={`tokenHolders.${index}.balance`} - variant="noCropBorder" + noBrokenCorners label="Balances" hideLabel={index > 0} control={control} - // isAsterickSign rules={{ required: true, pattern: patternOnlyNumbers }} placeHolder="0" />