Forráskód Böngészése

Feature: SelectBox base added.
Feature: BottomSheet start.

lfabl 2 hónapja
szülő
commit
c05a902362

+ 27 - 1
example/src/index.tsx

@@ -5,8 +5,10 @@ import {
 import {
     setupNCoreUIKit,
     NCoreUIKitTheme,
+    SelectBox,
     Button,
-    Text
+    Text,
+    BottomSheet
 } from "ncore-ui-kit-mobile";
 import {
     useFonts
@@ -19,6 +21,12 @@ const NCoreUIKitBase = setupNCoreUIKit({
     initialSelectedTheme: "dark"
 });
 
+const X = [{
+    t: "x",
+    p: "y",
+    n: 90
+}];
+
 const App = () => {
     const {
         colors
@@ -32,6 +40,21 @@ const App = () => {
             }
         ]}
     >
+        <SelectBox
+            keyExtractor={(data, index) => `${data.t}-${index}`}
+            titleExtractor={(data) => data.t}
+            subTitle="Deneme Subtitle"
+            hintText="Test deneme"
+            isShowSubTitle={true}
+            initialSelectedItems={[{
+                __title: X[0]?.t as string,
+                __key: `${X[0]?.t}-0`,
+                __originalIndex: 0
+            }]}
+            title="Deneme Box"
+            isRequired={true}
+            data={X}
+        />
         <TextInput
             variant="hidden"
         />
@@ -43,6 +66,9 @@ const App = () => {
             title="Ahmet"
             variant="filled"
         />
+        <BottomSheet>
+            <Text>Deneme 123</Text>
+        </BottomSheet>
     </View>;
 };
 

+ 69 - 0
src/components/bottomSheet/index.tsx

@@ -0,0 +1,69 @@
+import {
+    type FC
+} from "react";
+import {
+    View
+} from "react-native";
+import type IBottomSheetProps from "./type";
+import stylesheet from "./stylesheet";
+import {
+    NCoreUIKitTheme
+} from "../../core/hooks";
+import {
+    SafeAreaView
+} from "react-native-safe-area-context";
+import Modal from "../modal";
+
+const BottomSheet: FC<IBottomSheetProps> = ({
+    isWrapSafeareaContext = true,
+    safeAreaViewBackgroundColor,
+    backgroundColor = "default",
+    safeAreaViewStyle,
+    children,
+    style,
+    ...props
+}) => {
+    const {
+        colors,
+        spaces
+    } = NCoreUIKitTheme.useContext();
+
+    const renderView = () => {
+        return <View
+            {...props}
+            style={[
+                {
+                    backgroundColor: colors.content.container[backgroundColor],
+                    padding: spaces.spacingMd
+                },
+                stylesheet.container,
+                style
+            ]}
+        >
+            {children}
+        </View>;
+    };
+
+    const renderWithSafeareaView = () => {
+        return <SafeAreaView
+            style={[
+                safeAreaViewStyle,
+                {
+                    backgroundColor: safeAreaViewBackgroundColor ? colors.content.container[safeAreaViewBackgroundColor] : colors.content.container[backgroundColor]
+                },
+                stylesheet.safeAreaViewContainer
+            ]}
+        >
+            {renderView()}
+        </SafeAreaView>;
+    };
+
+    const renderSafeareaContext = () => {
+        return isWrapSafeareaContext ? renderWithSafeareaView() : renderView();
+    };
+
+    return <Modal>
+        {renderSafeareaContext()}
+    </Modal>;
+};
+export default BottomSheet;

+ 17 - 0
src/components/bottomSheet/stylesheet.ts

@@ -0,0 +1,17 @@
+import {
+    StyleSheet
+} from "react-native";
+
+const stylesheet = StyleSheet.create({
+    container: {
+        backgroundColor: "blue",
+        position: "absolute",
+        zIndex: 99999
+    },
+    safeAreaViewContainer: {
+        backgroundColor: "red",
+        position: "absolute",
+        zIndex: 99998
+    }
+});
+export default stylesheet;

+ 19 - 0
src/components/bottomSheet/type.ts

@@ -0,0 +1,19 @@
+import {
+    type ReactNode
+} from "react";
+import type {
+    StyleProp,
+    ViewStyle
+} from "react-native";
+
+interface IBottomSheetProps {
+    safeAreaViewBackgroundColor?: keyof NCoreUIKit.ContainerContentColors;
+    backgroundColor?: keyof NCoreUIKit.ContainerContentColors;
+    safeAreaViewStyle?: StyleProp<ViewStyle>;
+    isWrapSafeareaContext?: boolean;
+    style?: StyleProp<ViewStyle>;
+    children?: ReactNode;
+}
+export type {
+    IBottomSheetProps as default
+};

+ 5 - 2
src/components/index.ts

@@ -34,8 +34,11 @@ export {
 export {
     default as TextInput
 } from "./textInput";
-/*
+
 export {
     default as SelectBox
 } from "./selectBox";
-*/
+
+export {
+    default as BottomSheet
+} from "./bottomSheet";

+ 521 - 0
src/components/selectBox/index.tsx

@@ -0,0 +1,521 @@
+import {
+    useImperativeHandle,
+    forwardRef,
+    useReducer,
+    type Ref
+} from "react";
+import {
+    TouchableOpacity,
+    View
+} from "react-native";
+import {
+    type SelectBoxComponent,
+    type ISelectBoxRef,
+    type SelectBoxType,
+    type SelectedItem
+} from "./type";
+import type ISelectBoxProps from "./type";
+import stylesheet, {
+    getSelectBoxType,
+    useStyles
+} from "./stylesheet";
+import {
+    NCoreUIKitLocalize,
+    NCoreUIKitTheme
+} from "../../core/hooks";
+import {
+    type INCoreUIKitIconProps,
+    type NCoreUIKitIcon
+} from "../../types";
+import type ITextProps from "../text/type";
+import {
+    BadgeQuestionMarkIcon,
+    BadgeSuccessIcon,
+    BadgeDangerIcon,
+    BadgeAlertIcon,
+    BadgeInfoIcon,
+    CleanIcon
+} from "../../assets/svg";
+import Text from "../text";
+
+const SelectBoxTypeIcon: Record<Exclude<SelectBoxType, "default">, NCoreUIKitIcon> = {
+    "question": BadgeQuestionMarkIcon,
+    "success": BadgeSuccessIcon,
+    "warning": BadgeAlertIcon,
+    "danger": BadgeDangerIcon,
+    "info": BadgeInfoIcon
+};
+
+function SelectBox<T>({
+    rightIcon: RightIconComponentProp,
+    hintTextIcon: HintTextIconProp,
+    spreadBehaviour = "baseline",
+    isShowHintTextIcon = false,
+    icon: IconComponentProp,
+    hintTextContainerStyle,
+    isCleanEnabled = false,
+    contentContainerStyle,
+    subTitle = "Optional",
+    initialSelectedItems,
+    onFocus: onFocusProp,
+    isRequired = false,
+    isDisabled = false,
+    onBlur: onBlurProp,
+    hintTextIconStyle,
+    data: initialData,
+    variant = "text",
+    type = "default",
+    rightIconOnPress,
+    rightIconStyle,
+    isShowSubTitle,
+    cleanIconStyle,
+    titleExtractor,
+    keyExtractor,
+    contentStyle,
+    placeholder,
+    iconOnPress,
+    isOptional,
+    validation,
+    iconStyle,
+    onChange,
+    hintText,
+    style,
+    title
+}: ISelectBoxProps<T>, ref: Ref<ISelectBoxRef<T>>) {
+    const {
+        inlineSpaces,
+        typography,
+        radiuses,
+        borders,
+        spaces,
+        colors
+    } = NCoreUIKitTheme.useContext();
+
+    const {
+        localize
+    } = NCoreUIKitLocalize.useContext();
+
+    const currentType = getSelectBoxType({
+        type
+    });
+
+    const {
+        contentContainer: contentContainerDynamicStyle,
+        titleContainer: titleContainerDynamicStyle,
+        hintTextIcon: hintTextIconDynamicStyle,
+        contentText: contentTextDynamicStyle,
+        cleanButton: cleanButtonDynamicStyle,
+        rightIcon: rightIconDynamicStyle,
+        container: containerDynamicStyle,
+        hintText: hintTextDynamicStyle,
+        required: requiredDynamicStyle,
+        subTitle: subTitleDynamicStyle,
+        overlay: overlayDynamicStyle,
+        content: contentDynamicStyle,
+        title: titleDynamicStyle,
+        icon: iconDynamicStyle
+    } = useStyles({
+        icon: IconComponentProp ? true : false,
+        spreadBehaviour,
+        inlineSpaces,
+        currentType,
+        isDisabled,
+        typography,
+        radiuses,
+        borders,
+        spaces,
+        colors,
+        title,
+        type
+    });
+
+    const styleType = type === "default" ? "neutral" : type === "question" ? "neutral" : type === "danger" ? "error" : type;
+
+    const [
+        data,
+        setData
+    ] = useReducer<Array<T & SelectedItem>, [Array<T & SelectedItem>]>((_, newState) => {
+        return newState;
+    }, initialData.map((bIItem, bIIndex) => {
+        return {
+            ...bIItem,
+            __title: titleExtractor(bIItem, bIIndex),
+            __key: keyExtractor(bIItem, bIIndex),
+            __originalIndex: bIIndex
+        };
+    }));
+
+    const [
+        selectedItems,
+        setSelectedItems
+    ] = useReducer<Array<SelectedItem>, [Array<SelectedItem>]>((_, newState) => {
+        return newState;
+    }, initialSelectedItems ?? []);
+
+    useImperativeHandle(
+        ref,
+        () => ({
+            updateSelections,
+            cleanSelections,
+            updateData,
+            focus,
+            blur
+        }),
+        []
+    );
+
+    const titleProps: ITextProps = {
+        color: currentType.titleColor,
+        variant: "bodyLargeSize"
+    };
+
+    const iconProps: INCoreUIKitIconProps = {
+        size: Number(typography.labelLargeSize.fontSize) + 6,
+        color: currentType.iconColor
+    };
+
+    if(isDisabled) {
+        iconProps.color = "disabled";
+    }
+
+    const blur = () => {
+        // inputRef.current?.blur();
+    };
+
+    const focus = () => {
+        // inputRef.current?.focus();
+    };
+
+    const cleanSelections = () => {
+        setSelectedItems([]);
+
+        if(onChange) {
+            onChange([], data);
+        }
+    };
+
+    const updateData = (newData: Array<T>, newSelectedItems: Array<SelectedItem>) => {
+        const _newData = newData.map((bIItem, bIIndex) => {
+            return {
+                ...bIItem,
+                __title: titleExtractor(bIItem, bIIndex),
+                __key: keyExtractor(bIItem, bIIndex),
+                __originalIndex: bIIndex
+            };
+        });
+
+        setData(_newData);
+
+        const _newSelectedItems = JSON.parse(JSON.stringify(newSelectedItems ? newSelectedItems : selectedItems)).map((sItem: SelectedItem) => {
+            const originalDataIndex = _newData.findIndex(ssItem => ssItem.__key === sItem.__key);
+
+            return {
+                ...sItem,
+                __originalIndex: _newData[originalDataIndex]?.__originalIndex,
+                __title: _newData[originalDataIndex]?.__title,
+                __key: _newData[originalDataIndex]?.__key
+            };
+        });
+
+        setSelectedItems(_newSelectedItems);
+    };
+
+    const updateSelections = (newSelectedItems: Array<SelectedItem>) => {
+        const _newSelectedItems = JSON.parse(JSON.stringify(newSelectedItems)).map((sItem: SelectedItem) => {
+            const originalDataIndex = data.findIndex(ssItem => ssItem.__key === sItem.__key);
+
+            return {
+                ...sItem,
+                __originalIndex: data[originalDataIndex]?.__originalIndex,
+                __title: data[originalDataIndex]?.__title,
+                __key: data[originalDataIndex]?.__key
+            };
+        });
+
+        setSelectedItems(_newSelectedItems);
+    };
+
+    const onFocus = () => {
+        if(onFocusProp) onFocusProp();
+    };
+
+    const onBlur = () => {
+        if(onBlurProp) onBlurProp();
+    };
+
+    const renderCleanButton = () => {
+        if(isDisabled) {
+            return null;
+        }
+
+        if(variant !== "text") {
+            return null;
+        }
+
+        if(!isCleanEnabled || !selectedItems.length) {
+            return null;
+        }
+
+        return <TouchableOpacity
+            style={[
+                cleanIconStyle,
+                stylesheet.cleanButton,
+                cleanButtonDynamicStyle
+            ]}
+            onPress={() => {
+                setSelectedItems([]);
+
+                if(onChange) {
+                    onChange([]);
+                }
+            }}
+        >
+            <CleanIcon
+                color="mid"
+                size={20}
+            />
+        </TouchableOpacity>;
+    };
+
+    const renderIcon = () => {
+        if (!IconComponentProp) {
+            return null;
+        }
+
+        return <TouchableOpacity
+            onPress={iconOnPress}
+            style={[
+                iconStyle,
+                stylesheet.icon,
+                iconDynamicStyle
+            ]}
+        >
+            <IconComponentProp
+                color={iconProps.color}
+                size={iconProps.size}
+            />
+        </TouchableOpacity>;
+    };
+
+    const renderRightIcon = () => {
+        if (!RightIconComponentProp) {
+            return null;
+        }
+
+        if(isCleanEnabled && selectedItems.length > 0 && variant === "text") {
+            return null;
+        }
+
+        return <TouchableOpacity
+            onPress={rightIconOnPress}
+            style={[
+                rightIconStyle,
+                stylesheet.rightIcon,
+                rightIconDynamicStyle
+            ]}
+        >
+            <RightIconComponentProp
+                color={iconProps.color}
+                size={iconProps.size}
+            />
+        </TouchableOpacity>;
+    };
+
+    const renderHintIcon = () => {
+        if(!isShowHintTextIcon) {
+            return null;
+        }
+
+        if(HintTextIconProp) {
+            return <HintTextIconProp
+                color={isDisabled ? "disabled" : currentType.hintTextIconColor}
+                size={20}
+                style={[
+                    hintTextIconStyle,
+                    stylesheet.hintTextIcon,
+                    hintTextIconDynamicStyle
+                ]}
+            />;
+        }
+
+        const CurrentHintIcon = SelectBoxTypeIcon[type === "default" ? "question" : type];
+
+        return <CurrentHintIcon
+            customColor={isDisabled ? colors.system.state.content.disabled[styleType] : undefined}
+            color={currentType.hintTextIconColor}
+            size={20}
+            style={[
+                hintTextIconStyle,
+                stylesheet.hintTextIcon,
+                hintTextIconDynamicStyle
+            ]}
+        />;
+    };
+
+    const renderHintText = () => {
+        if (!hintText) {
+            return null;
+        }
+
+        return <View
+            style={[
+                hintTextContainerStyle,
+                stylesheet.hintText,
+                hintTextDynamicStyle
+            ]}
+        >
+            {renderHintIcon()}
+            <Text
+                customColor={isDisabled ? colors.system.state.content.disabled[styleType] : undefined}
+                color={currentType.hintTextColor}
+                variant="labelSmallSize"
+            >
+                {hintText}
+            </Text>
+        </View>;
+    };
+
+    const renderRequired = () => {
+        if(!isRequired) {
+            return null;
+        }
+
+        return <Text
+            color="danger"
+            style={[
+                stylesheet.required,
+                requiredDynamicStyle
+            ]}
+        >*</Text>;
+    };
+
+    const renderSubtitle = () => {
+        if(!isShowSubTitle && !isOptional) {
+            return null;
+        }
+
+        return <Text
+            variant="labelLargeSize"
+            color={titleProps.color}
+            style={[
+                stylesheet.subTitle,
+                subTitleDynamicStyle
+            ]}
+        >
+            ( {isOptional ? localize("is-optional") : subTitle} )
+        </Text>;
+    };
+
+    const renderTitle = () => {
+        if (!title) {
+            return null;
+        }
+
+        return <TouchableOpacity
+            style={[
+                stylesheet.titleContainer,
+                titleContainerDynamicStyle
+            ]}
+            onPress={() => {
+                if(!isDisabled) {
+
+                }
+            }}
+        >
+            {renderRequired()}
+            <Text
+                {...titleProps}
+                variant={titleProps.variant}
+                color={titleProps.color}
+                style={[
+                    stylesheet.title,
+                    titleDynamicStyle
+                ]}
+            >
+                {title}
+            </Text>
+            {renderSubtitle()}
+        </TouchableOpacity>;
+    };
+
+    const renderValue = () => {
+        if(!selectedItems.length) {
+            return <Text
+                style={[
+                    stylesheet.contentText,
+                    contentTextDynamicStyle
+                ]}
+            >
+                {placeholder ? placeholder : localize("select-an-option")}
+            </Text>;
+        }
+
+        return <Text
+            style={[
+                stylesheet.contentText,
+                contentTextDynamicStyle
+            ]}
+        >
+            {selectedItems.length > 1 ? localize("selected-options-with-count", [
+                selectedItems.length
+            ]) : selectedItems[0]?.__title}
+        </Text>;
+    };
+
+    const renderContent = () => {
+        return <View
+            style={[
+                contentStyle,
+                stylesheet.content,
+                contentDynamicStyle
+            ]}
+        >
+            {renderValue()}
+        </View>;
+    };
+
+    const renderOverlay = () => {
+        return <View
+            style={[
+                stylesheet.overlay,
+                overlayDynamicStyle
+            ]}
+        />;
+    };
+
+    return <View
+        style={[
+            style,
+            stylesheet.container,
+            containerDynamicStyle
+        ]}
+    >
+        {renderTitle()}
+
+        <TouchableOpacity
+            disabled={isDisabled}
+            style={[
+                contentContainerStyle,
+                stylesheet.contentContainer,
+                contentContainerDynamicStyle
+            ]}
+            onPress={() => {
+                if(!isDisabled) {
+
+                }
+            }}
+        >
+            {renderOverlay()}
+
+            {renderIcon()}
+
+            {renderContent()}
+
+            {renderCleanButton()}
+            {renderRightIcon()}
+        </TouchableOpacity>
+
+        {renderHintText()}
+    </View>;
+};
+export default forwardRef(SelectBox) as unknown as SelectBoxComponent;

+ 252 - 0
src/components/selectBox/stylesheet.ts

@@ -0,0 +1,252 @@
+import {
+    type ViewStyle,
+    type TextStyle,
+    StyleSheet
+} from "react-native";
+import {
+    type SelectBoxDynamicStyleType,
+    type SelectBoxTypes,
+    type SelectBoxType
+} from "./type";
+import type {
+    Mutable
+} from "../../types";
+
+export const TEXT_INPUT_TYPE_STYLES: Record<
+    SelectBoxType,
+    SelectBoxTypes
+> = {
+    default: {
+        focusBorderColor: "emphasized",
+        borderColor: "emphasized",
+        hintTextIconColor: "mid",
+        placeholderColor: "low",
+        containerColor: "mid",
+        hintTextColor: "mid",
+        titleColor: "high",
+        inputColor: "mid",
+        iconColor: "mid"
+    },
+    danger: {
+        placeholderColor: "dangerLow",
+        hintTextIconColor: "danger",
+        focusBorderColor: "danger",
+        containerColor: "danger",
+        hintTextColor: "danger",
+        borderColor: "danger",
+        titleColor: "danger",
+        inputColor: "danger",
+        iconColor: "danger"
+    },
+    success: {
+        placeholderColor: "successLow",
+        hintTextIconColor: "success",
+        focusBorderColor: "success",
+        containerColor: "success",
+        hintTextColor: "success",
+        borderColor: "success",
+        titleColor: "success",
+        inputColor: "success",
+        iconColor: "success"
+    },
+    warning: {
+        placeholderColor: "warningLow",
+        hintTextIconColor: "warning",
+        focusBorderColor: "warning",
+        containerColor: "warning",
+        hintTextColor: "warning",
+        borderColor: "warning",
+        titleColor: "warning",
+        inputColor: "warning",
+        iconColor: "warning"
+    },
+    info: {
+        placeholderColor: "infoLow",
+        hintTextIconColor: "info",
+        focusBorderColor: "info",
+        containerColor: "info",
+        hintTextColor: "info",
+        borderColor: "info",
+        titleColor: "info",
+        inputColor: "info",
+        iconColor: "info"
+    },
+    question: {
+        focusBorderColor: "emphasized",
+        borderColor: "emphasized",
+        hintTextIconColor: "mid",
+        placeholderColor: "low",
+        containerColor: "mid",
+        hintTextColor: "mid",
+        titleColor: "mid",
+        inputColor: "mid",
+        iconColor: "mid"
+    }
+};
+
+export const getSelectBoxType = ({
+    type
+}: {
+    type: SelectBoxType;
+}) => {
+    const currentType = TEXT_INPUT_TYPE_STYLES[type];
+
+    return currentType;
+};
+// TODO: PLACECHOLDER CODE ADD.
+const stylesheet = StyleSheet.create({
+    container: {
+        flexDirection: "column",
+        boxSizing: "border-box"
+    },
+    contentContainer: {
+        boxSizing: "border-box",
+        flexDirection: "row",
+        borderStyle: "solid",
+        alignItems: "center",
+        position: "relative",
+        minWidth: 250
+    },
+    content: {
+
+    },
+    contentText: {
+        minHeight: 20
+    },
+    cleanButton: {
+        justifyContent: "center",
+        alignItems: "center",
+        alignSelf: "center",
+        zIndex: 99
+    },
+    titleContainer: {
+        flexDirection: "row",
+        alignItems: "center"
+    },
+    title: {
+
+    },
+    icon: {
+        justifyContent: "center",
+        alignContent: "center",
+        alignItems: "center",
+        zIndex: 99
+    },
+    required: {
+    },
+    hintTextIcon: {
+    },
+    hintText: {
+        flexDirection: "row",
+        alignItems: "center"
+    },
+    subTitle: {
+
+    },
+    overlay: {
+        position: "absolute",
+        zIndex: 98,
+        bottom: 0,
+        right: 0,
+        left: 0,
+        top: 0
+    },
+    rightIcon: {
+        justifyContent: "center",
+        alignItems: "center",
+        alignSelf: "center",
+        zIndex: 99
+    }
+});
+
+export const useStyles = ({
+    spreadBehaviour,
+    inlineSpaces,
+    currentType,
+    typography,
+    isDisabled,
+    radiuses,
+    borders,
+    colors,
+    spaces,
+    type
+}: SelectBoxDynamicStyleType) => {
+    const styleType = type === "danger" ? "error" : type === "question" ? "neutral" : type;
+
+    const styles = {
+        container: {
+        } as Mutable<ViewStyle>,
+        contentContainer: {
+            backgroundColor: colors.content.container[currentType.containerColor],
+            borderColor: colors.content.container[currentType.containerColor],
+            paddingBottom: spaces.spacingMd,
+            paddingRight: spaces.spacingMd,
+            borderRadius: radiuses.actions,
+            paddingLeft: spaces.spacingMd,
+            paddingTop: spaces.spacingMd,
+            borderWidth: borders.line
+        } as Mutable<ViewStyle>,
+        content: {
+
+        } as Mutable<ViewStyle>,
+        contentText: {
+
+        } as Mutable<TextStyle>,
+        input: {
+            ...typography.labelLargeSize,
+            color: colors.content.text[currentType.inputColor]
+        } as Mutable<TextStyle>,
+        cleanButton: {
+            marginLeft: spaces.spacingSm
+        } as Mutable<ViewStyle>,
+        titleContainer: {
+            marginBottom: spaces.spacingSm
+        } as Mutable<ViewStyle>,
+        title: {
+
+        } as Mutable<TextStyle>,
+        icon: {
+            marginRight: spaces.spacingSm
+        } as Mutable<ViewStyle>,
+        required: {
+            marginRight: inlineSpaces.required
+        } as Mutable<ViewStyle>,
+        hintTextIcon: {
+            marginRight: spaces.spacingXs
+        } as Mutable<ViewStyle>,
+        hintText: {
+            marginTop: spaces.spacingSm
+        } as Mutable<TextStyle>,
+        subTitle: {
+            marginLeft: inlineSpaces.subTitle
+        } as Mutable<TextStyle>,
+        overlay: {
+            borderRadius: radiuses.actions - 1
+        } as Mutable<ViewStyle>,
+        rightIcon: {
+            marginLeft: spaces.spacingSm
+        } as Mutable<ViewStyle>
+    };
+
+    if(isDisabled) {
+        const disableStyleType = styleType === "default" ? "neutral" : styleType;
+
+        styles.overlay.backgroundColor = colors.system.state.overlay.disabled[disableStyleType];
+        styles.container.borderColor = colors.system.state.overlay.disabled[disableStyleType];
+
+        styles.input.color = colors.content.text.disabled;
+    }
+
+    if (spreadBehaviour === "baseline") {
+        styles.container.alignSelf = spreadBehaviour;
+        styles.container.width = "auto";
+    }
+
+    if (spreadBehaviour === "stretch") {
+        styles.container.justifyContent = "center";
+        styles.container.width = "100%";
+    }
+
+    return styles;
+};
+export default stylesheet;

+ 102 - 0
src/components/selectBox/type.ts

@@ -0,0 +1,102 @@
+import type {
+    ReactNode,
+    Ref
+} from "react";
+import {
+    type ViewStyle
+} from "react-native";
+import {
+    type NCoreUIKitIcon
+} from "../../types";
+
+export type ISelectBoxRef<T> = {
+    updateData: (newData: Array<T>, newSelectedItems: Array<SelectedItem>) => void;
+    updateSelections: (newSelectedItems: Array<SelectedItem>) => void;
+    cleanSelections: () => void;
+    focus: () => void;
+    blur: () => void;
+};
+
+export type SelectBoxDynamicStyleType = {
+    inlineSpaces: NCoreUIKit.ActivePalette["inlineSpaces"];
+    typography: NCoreUIKit.ActivePalette["typography"];
+    radiuses: NCoreUIKit.ActivePalette["radiuses"];
+    spreadBehaviour?: SelectBoxSpreadBehaviour;
+    spaces: NCoreUIKit.ActivePalette["spaces"];
+    colors: NCoreUIKit.ActivePalette["colors"];
+    currentType: SelectBoxTypes;
+    borders: NCoreUIKit.Borders;
+    isDisabled?: boolean;
+    type: SelectBoxType;
+    title?: string;
+    icon?: boolean;
+};
+
+export type SelectBoxTypes = {
+    containerColor: keyof NCoreUIKit.ContainerContentColors;
+    focusBorderColor: keyof NCoreUIKit.BorderContentColors;
+    hintTextIconColor: keyof NCoreUIKit.IconContentColors;
+    placeholderColor: keyof NCoreUIKit.TextContentColors;
+    hintTextColor: keyof NCoreUIKit.TextContentColors;
+    borderColor: keyof NCoreUIKit.BorderContentColors;
+    inputColor: keyof NCoreUIKit.TextContentColors;
+    titleColor: keyof NCoreUIKit.TextContentColors;
+    iconColor: keyof NCoreUIKit.IconContentColors;
+};
+
+export type SelectBoxType = "default" | "danger" | "warning" | "question" | "success" | "info";
+
+export type SelectBoxSpreadBehaviour = "baseline" | "stretch" | "free";
+
+export type SelectBoxVariant = "text" | "hidden";
+
+export type SelectBoxComponent = <T>(
+  props: ISelectBoxProps<T> & { ref?: Ref<ISelectBoxRef<T>> }
+) => ReactNode;
+
+export type SelectedItem = {
+    __originalIndex: number;
+    __title: string;
+    __key: string;
+};
+
+interface ISelectBoxProps<T> {
+    onChange?: (selectedItems: Array<SelectedItem>, data: Array<T>) => void;
+    titleExtractor: (item: T, index: number) => string;
+    keyExtractor: (item: T, index: number) => string;
+    initialSelectedItems?: Array<SelectedItem>;
+    spreadBehaviour?: SelectBoxSpreadBehaviour;
+    validation?: (text: string) => boolean;
+    hintTextContainerStyle?: ViewStyle;
+    contentContainerStyle?: ViewStyle;
+    hintTextIconStyle?: ViewStyle;
+    rightIconOnPress?: () => void;
+    hintTextIcon?: NCoreUIKitIcon;
+    isShowHintTextIcon?: boolean;
+    cleanIconStyle?: ViewStyle;
+    rightIconStyle?: ViewStyle;
+    rightIcon?: NCoreUIKitIcon;
+    variant?: SelectBoxVariant;
+    isShowSubTitle?: boolean;
+    isCleanEnabled?: boolean;
+    iconOnPress?: () => void;
+    contentStyle?: ViewStyle;
+    iconStyle?: ViewStyle;
+    icon?: NCoreUIKitIcon;
+    placeholder?: string;
+    isRequired?: boolean;
+    isDisabled?: boolean;
+    isOptional?: boolean;
+    onFocus?: () => void;
+    type?: SelectBoxType;
+    onBlur?: () => void;
+    hintText?: string;
+    subTitle?: string;
+    style?: ViewStyle;
+    title?: string;
+    data: Array<T>;
+    id?: string;
+};
+export type {
+    ISelectBoxProps as default
+};

+ 18 - 9
src/components/textInput/index.tsx

@@ -54,6 +54,7 @@ const TextInput: RefForwardingComponent<ITextInputRef, ITextInputProps> = ({
     hintTextIcon: HintTextIconProp,
     spreadBehaviour = "baseline",
     isShowHideTextButton = true,
+    isShowHintTextIcon = false,
     icon: IconComponentProp,
     hintTextContainerStyle,
     isCleanEnabled = false,
@@ -102,6 +103,7 @@ const TextInput: RefForwardingComponent<ITextInputRef, ITextInputProps> = ({
 
     const {
         hideTextIconContainer: hideTextIconContainerDynamicStyle,
+        titleContainer: titleContainerDynamicStyle,
         hintTextIcon: hintTextIconDynamicStyle,
         cleanButton: cleanButtonDynamicStyle,
         rightIcon: rightIconDynamicStyle,
@@ -315,7 +317,7 @@ const TextInput: RefForwardingComponent<ITextInputRef, ITextInputProps> = ({
     };
 
     const renderHintIcon = () => {
-        if(!type) {
+        if(!isShowHintTextIcon) {
             return null;
         }
 
@@ -402,19 +404,26 @@ const TextInput: RefForwardingComponent<ITextInputRef, ITextInputProps> = ({
             return null;
         }
 
-        return <Text
-            {...titleProps}
-            variant={titleProps.variant}
-            color={titleProps.color}
+        return <View
             style={[
-                stylesheet.title,
-                titleDynamicStyle
+                stylesheet.titleContainer,
+                titleContainerDynamicStyle
             ]}
         >
             {renderRequired()}
-            {title}
+            <Text
+                {...titleProps}
+                variant={titleProps.variant}
+                color={titleProps.color}
+                style={[
+                    stylesheet.title,
+                    titleDynamicStyle
+                ]}
+            >
+                {title}
+            </Text>
             {renderSubtitle()}
-        </Text>;
+        </View>;
     };
 
     const renderInput = () => {

+ 8 - 1
src/components/textInput/stylesheet.ts

@@ -137,6 +137,10 @@ const stylesheet = StyleSheet.create({
         zIndex: 99,
         height: 18
     },
+    titleContainer: {
+        flexDirection: "row",
+        alignItems: "center"
+    },
     title: {
     },
     icon: {
@@ -211,9 +215,12 @@ export const useStyles = ({
         hideTextIconContainer: {
             marginLeft: spaces.spacingSm
         } as Mutable<ViewStyle>,
-        title: {
+        titleContainer: {
             marginBottom: spaces.spacingSm
         } as Mutable<ViewStyle>,
+        title: {
+
+        } as Mutable<TextStyle>,
         icon: {
             marginRight: spaces.spacingSm
         } as Mutable<ViewStyle>,

+ 1 - 0
src/components/textInput/type.ts

@@ -62,6 +62,7 @@ interface ITextInputProps extends TextInputProps {
     hideTextIconStyle?: ViewStyle;
     rightIconOnPress?: () => void;
     hintTextIcon?: NCoreUIKitIcon;
+    isShowHintTextIcon?: boolean;
     cleanIconStyle?: ViewStyle;
     rightIconStyle?: ViewStyle;
     rightIcon?: NCoreUIKitIcon;

+ 2 - 0
src/index.tsx

@@ -10,7 +10,9 @@ export {
 
 export {
     PageContainer,
+    BottomSheet,
     TextInput,
+    SelectBox,
     Loading,
     // Dialog,
     Button,

+ 4 - 0
src/variants/locales/default.json

@@ -3,6 +3,8 @@
         "locale": "tr-TR",
         "isRTL": false,
         "translations": {
+            "selected-options-with-count": "{{0}} items selected",
+            "select-an-option": "Select an option",
             "is-optional": "Opsiyonel",
             "cancel": "İptal",
             "ok": "Tamam"
@@ -12,6 +14,8 @@
         "locale": "en-US",
         "isRTL": false,
         "translations": {
+            "selected-options-with-count": "{{0}} seçim yapıldı",
+            "select-an-option": "Seçim yapın,",
             "is-optional": "Optional",
             "cancel": "Cancel",
             "ok": "Okey"

+ 2 - 2
src/variants/themes/default.json

@@ -43,8 +43,8 @@
             }
         },
         "inlineSpaces": {
-            "subTitle": 2,
-            "required": 2
+            "subTitle": 4,
+            "required": 4
         },
         "borders": {
             "line": 2