Ver código fonte

Feature: TimeSelector base completed.

lfabl 3 semanas atrás
pai
commit
02bc332431

+ 15 - 0
siradakiler.txt

@@ -0,0 +1,15 @@
+* saat seçim sistemi.
+    -> minTime ve maxTime işleyecez. (minDate) (maxDate)'den çekilebilir.
+    -> Time selector date yokken center çalışsın. Prop'a bağlı yaparsan iyi olur.
+    -> input blur olma durumu çözülecek. (sanki key key setliyor. 2 harf aynı anda setlenemiyor. muhtemelen debouncer bunu çözer.)
+    -> debouncer eklenecek.
+    -> Date değiştiğinde tarihin üzerine yazımı eklenecek.
+    -> Seconds da test edilecek.
+    -> Düz date test edilecek.
+* ay seçimi.
+* yıl seçimi.
+* hızlı filtreler.
+* özel tarih seçerek gitme.
+* rrule sistemi.
+
+-> STATE CARD

+ 0 - 5
src/components/dateSelector/index.tsx

@@ -45,7 +45,6 @@ const DateSelector = ({
     selectMultipleObject,
     selectObject,
     dateRange,
-    dateRule,
     maxDate,
     minDate,
     variant,
@@ -60,7 +59,6 @@ const DateSelector = ({
 
     const selectDate = dateSelect({
         dateRange,
-        dateRule,
         variant,
         date
     });
@@ -99,7 +97,6 @@ const DateSelector = ({
     }, [
         dateRange,
         viewDate,
-        dateRule,
         date
     ]);
 
@@ -279,8 +276,6 @@ const DateSelector = ({
                         start: dayItem.date.toDate(),
                         end: undefined
                     });
-                } else {
-                    console.log("rrule");
                 }
             }}
             style={[

+ 0 - 6
src/components/dateSelector/type.ts

@@ -7,9 +7,6 @@ import type {
     DateTimePickerVariant
 } from "../dateTimePicker/type";
 import type moment from "moment";
-import type {
-    RRule
-} from "rrule";
 
 export interface IDateSelectorRef {
     changeCurrentMonth: (date: Date) => void;
@@ -36,7 +33,6 @@ export interface CalendarDay {
 export type DateSelectInitialGeneratorType = {
     dateRange?: DateTimePickerDateRange;
     variant: DateTimePickerVariant;
-    dateRule?: RRule;
     date?: Date;
 };
 
@@ -45,14 +41,12 @@ export type DayOfWeekLengthType = "short" | "very-short" | "long";
 interface IDateSelectorProps {
     selectMultipleObject: (dateRange?: DateTimePickerDateRange) => void;
     setIsSheetContentReady: Dispatch<SetStateAction<boolean>>;
-    selectDateRule: (dateRule: RRule) => void;
     dayOfWeekLength?: DayOfWeekLengthType;
     selectObject: (date?: Date) => void;
     localeBasedFirstDayOfWeek?: boolean;
     dateRange?: DateTimePickerDateRange;
     isShowTodayIndicator?: boolean;
     variant: DateTimePickerVariant;
-    dateRule?: RRule;
     viewDate?: Date;
     maxDate?: Date;
     minDate?: Date;

+ 0 - 6
src/components/dateSelector/util.ts

@@ -4,7 +4,6 @@ import type {
 
 export const dateSelect = ({
     dateRange,
-    dateRule,
     variant,
     date
 }: DateSelectInitialGeneratorType): Date => {
@@ -17,10 +16,5 @@ export const dateSelect = ({
         if(dateRange.end) return dateRange.end;
     }
 
-    if(dateRule) {
-        const rrule = dateRule.all();
-        if(rrule[0]) return new Date(rrule[0]);
-    }
-
     return new Date();
 };

+ 30 - 16
src/components/dateTimeSheet/index.tsx

@@ -13,6 +13,7 @@ import {
     NCoreUIKitTheme
 } from "../../core/hooks";
 import DateSelector from "../dateSelector";
+import TimeSelector from "../timeSelector";
 import BottomSheet from "../bottomSheet";
 import CheckBox from "../checkBox";
 import Loading from "../loading";
@@ -31,10 +32,10 @@ const DateTimeSheet = ({
     bottomSheetProps,
     bottomSheetRef,
     customLocalize,
-    selectDateRule,
     selectObject,
     customTheme,
     setIsActive,
+    pickerType,
     dateRange,
     maxChoice,
     minChoice,
@@ -96,21 +97,34 @@ const DateTimeSheet = ({
                 contentContainerDynamicStyle
             ]}
         >
-            <DateSelector
-                localeBasedFirstDayOfWeek={localeBasedFirstDayOfWeek}
-                setIsSheetContentReady={setIsSheetContentReady}
-                selectMultipleObject={selectMultipleObject}
-                isShowTodayIndicator={isShowTodayIndicator}
-                dayOfWeekLength={dayOfWeekLength}
-                selectDateRule={selectDateRule}
-                selectObject={selectObject}
-                dateRange={dateRange}
-                dateRule={dateRule}
-                maxDate={maxDate}
-                minDate={minDate}
-                variant={variant}
-                date={date}
-            />
+            {
+                pickerType === "time" ? null : <DateSelector
+                    localeBasedFirstDayOfWeek={localeBasedFirstDayOfWeek}
+                    setIsSheetContentReady={setIsSheetContentReady}
+                    selectMultipleObject={selectMultipleObject}
+                    isShowTodayIndicator={isShowTodayIndicator}
+                    dayOfWeekLength={dayOfWeekLength}
+                    selectObject={selectObject}
+                    dateRange={dateRange}
+                    maxDate={maxDate}
+                    minDate={minDate}
+                    variant={variant}
+                    date={date}
+                />
+            }
+            {
+                pickerType === "date" ? null : <TimeSelector
+                    setIsSheetContentReady={setIsSheetContentReady}
+                    selectMultipleObject={selectMultipleObject}
+                    selectObject={selectObject}
+                    pickerType={pickerType}
+                    dateRange={dateRange}
+                    maxDate={maxDate}
+                    minDate={minDate}
+                    variant={variant}
+                    date={date}
+                />
+            }
         </View>;
     };
 

+ 4 - 0
src/components/index.ts

@@ -110,6 +110,10 @@ export type {
     IDateSelectorRef
 } from "./dateSelector/type";
 
+export {
+    default as NumericInput
+} from "./numericInput";
+
 export {
     default as MarkdownViewer
 } from "./markdownViewer";

+ 590 - 0
src/components/numericInput/index.tsx

@@ -0,0 +1,590 @@
+import {
+    useImperativeHandle,
+    forwardRef,
+    useState,
+    useRef
+} from "react";
+import {
+    TextInput as NativeTextInput,
+    TouchableOpacity,
+    Keyboard,
+    View
+} from "react-native";
+import type ITextInputProps from "./type";
+import {
+    type NumericInputInstance,
+    type INumericInputRef,
+    type NumericInputType
+} from "./type";
+import stylesheet, {
+    getNumericInputType,
+    useStyles
+} from "./stylesheet";
+import {
+    NCoreUIKitLocalize,
+    NCoreUIKitTheme
+} from "../../core/hooks";
+import {
+    type RefForwardingComponent
+} from "../../types";
+import type ITextProps from "../text/type";
+import {
+    BadgeQuestionMark as BadgeQuestionMarkIcon,
+    BadgeAlert as BadgeAlertIcon,
+    BadgeCheck as BadgeCheckIcon,
+    BadgeInfo as BadgeInfoIcon,
+    EyeClosed as EyeClosedIcon,
+    CircleX as CircleXIcon,
+    BadgeX as BadgeXIcon,
+    type LucideIcon,
+    Eye as EyeIcon
+} from "lucide-react-native";
+import Text from "../text";
+
+const NumericInputTypeIcon: Record<Exclude<NumericInputType, "default">, LucideIcon> = {
+    "question": BadgeQuestionMarkIcon,
+    "success": BadgeCheckIcon,
+    "warning": BadgeAlertIcon,
+    "info": BadgeInfoIcon,
+    "danger": BadgeXIcon
+};
+
+const NumericInput: RefForwardingComponent<INumericInputRef, ITextInputProps> = ({
+    isAutoKeyboardDismissOnBlur = true,
+    rightIcon: RightIconComponentProp,
+    hintTextIcon: HintTextIconProp,
+    spreadBehaviour = "baseline",
+    isShowHideTextButton = true,
+    isShowHintTextIcon = false,
+    icon: IconComponentProp,
+    hintTextContainerStyle,
+    isCleanEnabled = false,
+    contentContainerStyle,
+    subTitle = "Optional",
+    onFocus: onFocusProp,
+    isRequired = false,
+    isDisabled = false,
+    onBlur: onBlurProp,
+    hideTextIconStyle,
+    variant = "text",
+    type = "default",
+    rightIconOnPress,
+    customLocalize,
+    rightIconStyle,
+    isShowSubTitle,
+    cleanIconStyle,
+    onChangeText,
+    initialValue,
+    customTheme,
+    placeholder,
+    iconOnPress,
+    isOptional,
+    validation,
+    inputStyle,
+    iconStyle,
+    hintText,
+    style,
+    title,
+    ...props
+}, ref) => {
+    const {
+        inlineSpaces,
+        typography,
+        radiuses,
+        borders,
+        spaces,
+        colors
+    } = NCoreUIKitTheme.useContext(customTheme);
+
+    const {
+        localize
+    } = NCoreUIKitLocalize.useContext(customLocalize);
+
+    const currentType = getNumericInputType({
+        type
+    });
+
+    const inputRef = useRef<NumericInputInstance | null>(null);
+
+    const styleType = type === "default" ? "neutral" : type === "question" ? "neutral" : type === "danger" ? "error" : type;
+
+    const [
+        value,
+        setValue
+    ] = useState(initialValue ? initialValue : "");
+
+    const [
+        hideValue,
+        setHideValue
+    ] = useState(true);
+
+    const [
+        isFocused,
+        setIsFocused
+    ] = useState(false);
+
+    const [
+        selection,
+        setSelection
+    ] = useState({
+        start: 0,
+        end: 0
+    });
+
+    const {
+        hideTextIconContainer: hideTextIconContainerDynamicStyle,
+        titleContainer: titleContainerDynamicStyle,
+        hintTextIcon: hintTextIconDynamicStyle,
+        cleanButton: cleanButtonDynamicStyle,
+        rightIcon: rightIconDynamicStyle,
+        container: containerDynamicStyle,
+        hintText: hintTextDynamicStyle,
+        required: requiredDynamicStyle,
+        subTitle: subTitleDynamicStyle,
+        content: contentDynamicStyle,
+        overlay: overlayDynamicStyle,
+        title: titleDynamicStyle,
+        input: inputDynamicStyle,
+        icon: iconDynamicStyle
+    } = useStyles({
+        icon: IconComponentProp ? true : false,
+        spreadBehaviour,
+        inlineSpaces,
+        currentType,
+        isDisabled,
+        typography,
+        isFocused,
+        radiuses,
+        borders,
+        spaces,
+        colors,
+        title,
+        type
+    });
+
+    useImperativeHandle(
+        ref,
+        () => ({
+            updateValue,
+            cleanText,
+            focus,
+            blur
+        }),
+        []
+    );
+
+    const titleProps: ITextProps = {
+        color: currentType.titleColor,
+        variant: "bodyLargeSize"
+    };
+
+    const iconProps: NCoreUIKit.IconCallbackProps = {
+        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 cleanText = () => {
+        setValue("");
+
+        inputRef.current?.clear();
+
+        if(onChangeText) {
+            onChangeText("");
+        }
+    };
+
+    const updateValue = (text: string) => {
+        setValue(text);
+
+        inputRef.current?.setNativeProps({
+            text: text
+        });
+    };
+
+    const onFocus = () => {
+        setIsFocused(true);
+
+        if(onFocusProp) onFocusProp();
+    };
+
+    const onBlur = () => {
+        setIsFocused(false);
+
+        inputRef.current?.blur();
+
+        if(isAutoKeyboardDismissOnBlur) {
+            Keyboard.dismiss();
+        }
+
+        if(onBlurProp) onBlurProp();
+    };
+
+    const toggleValueVisibility = () => {
+        setHideValue(prevState => !prevState);
+    };
+
+    const handleTextChange = (text: string) => {
+        if (
+            props.maxLength &&
+            selection.start >= value.length &&
+            text.length > props.maxLength &&
+            text.length > value.length
+        ) {
+            inputRef.current?.setNativeProps({
+                value: value
+            });
+
+            return;
+        }
+
+        const cleanedText = text.replace(/\n/g, "").replace(/[^0-9]/g, "");
+        const newValue = props.maxLength ? cleanedText.slice(0, props.maxLength) : cleanedText;
+
+        if(text === "") {
+            setValue("");
+
+            if(onChangeText) onChangeText("");
+
+            return;
+        }
+
+        const isValid = validation ? validation(newValue) : true;
+
+        if (!isValid) {
+            setSelection({
+                start: selection.start,
+                end: selection.end
+            });
+
+            return;
+        }
+
+        const isDeleting = text.length < value.length;
+        let nextPosition = selection.start;
+
+        if (isDeleting) {
+            nextPosition = Math.max(0, selection.start - 1);
+        } else {
+            nextPosition = Math.min(newValue.length, selection.start + 1);
+        }
+
+        setValue(newValue);
+        if (onChangeText) onChangeText(newValue);
+
+        setSelection({
+            start: nextPosition,
+            end: nextPosition
+        });
+    };
+
+    const renderCleanButton = () => {
+        if(isDisabled) {
+            return null;
+        }
+
+        if(variant !== "text") {
+            return null;
+        }
+
+        if(!isCleanEnabled || !value.length) {
+            return null;
+        }
+
+        return <TouchableOpacity
+            style={[
+                cleanIconStyle,
+                stylesheet.cleanButton,
+                cleanButtonDynamicStyle
+            ]}
+            onPress={() => {
+                if(inputRef.current) inputRef.current.clear();
+
+                if(onChangeText) {
+                    onChangeText("");
+                }
+
+                setValue("");
+            }}
+        >
+            <CircleXIcon
+                color={colors.content.icon[currentType.iconColor]}
+                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(isShowHideTextButton && variant === "hidden") {
+            return null;
+        }
+
+        if(isCleanEnabled && value.length > 0 && variant === "text") {
+            return null;
+        }
+
+        return <TouchableOpacity
+            onPress={rightIconOnPress}
+            style={[
+                rightIconStyle,
+                stylesheet.rightIcon,
+                rightIconDynamicStyle
+            ]}
+        >
+            <RightIconComponentProp
+                color={iconProps.color}
+                size={iconProps.size}
+            />
+        </TouchableOpacity>;
+    };
+
+    const renderHideTextIcon = () => {
+        if(!isShowHideTextButton) {
+            return null;
+        }
+
+        if (variant !== "hidden") {
+            return null;
+        }
+
+        return <TouchableOpacity
+            onPress={toggleValueVisibility}
+            style={[
+                hideTextIconStyle,
+                stylesheet.hideTextIconContainer,
+                hideTextIconContainerDynamicStyle
+            ]}
+        >
+            {hideValue ?
+                <EyeIcon
+                    color={colors.content.icon[iconProps.color]}
+                    size={20}
+                /> :
+                <EyeClosedIcon
+                    color={colors.content.icon[iconProps.color]}
+                    size={20}
+                />
+            }
+        </TouchableOpacity>;
+    };
+
+    const renderHintIcon = () => {
+        if(!isShowHintTextIcon) {
+            return null;
+        }
+
+        if(HintTextIconProp) {
+            return <HintTextIconProp
+                color={isDisabled ? "disabled" : currentType.hintTextIconColor}
+                size={20}
+                style={[
+                    stylesheet.hintTextIcon,
+                    hintTextIconDynamicStyle
+                ]}
+            />;
+        }
+
+        const CurrentHintIcon = NumericInputTypeIcon[type === "default" ? "question" : type];
+
+        let hintIconColor = colors.content.icon[currentType.hintTextIconColor];
+
+        if(isDisabled) {
+            hintIconColor = colors.system.state.content.disabled[styleType];
+        }
+
+        return <CurrentHintIcon
+            color={hintIconColor}
+            size={20}
+            style={[
+                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 <View
+            style={[
+                stylesheet.titleContainer,
+                titleContainerDynamicStyle
+            ]}
+        >
+            {renderRequired()}
+            <Text
+                {...titleProps}
+                variant={titleProps.variant}
+                color={titleProps.color}
+                style={[
+                    stylesheet.title,
+                    titleDynamicStyle
+                ]}
+            >
+                {title}
+            </Text>
+            {renderSubtitle()}
+        </View>;
+    };
+
+    const renderInput = () => {
+        return <NativeTextInput
+            {...props}
+            placeholderTextColor={colors.content.text[currentType.placeholderColor]}
+            onSelectionChange={(e) => setSelection(e.nativeEvent.selection)}
+            secureTextEntry={variant === "hidden" && hideValue}
+            underlineColorAndroid="rgba(255,255,255,0)"
+            onChangeText={handleTextChange}
+            placeholder={placeholder}
+            allowFontScaling={false}
+            editable={!isDisabled}
+            maxLength={undefined}
+            selection={selection}
+            multiline={false}
+            onFocus={onFocus}
+            onBlur={onBlur}
+            value={value}
+            ref={inputRef}
+            style={[
+                inputStyle,
+                stylesheet.input,
+                inputDynamicStyle
+            ]}
+        />;
+    };
+
+    const renderOverlay = () => {
+        return <View
+            style={[
+                stylesheet.overlay,
+                overlayDynamicStyle
+            ]}
+        />;
+    };
+
+    return <TouchableOpacity
+        disabled={isDisabled}
+        style={[
+            style,
+            stylesheet.container,
+            containerDynamicStyle
+        ]}
+        onPress={() => {
+            if(!isDisabled) inputRef.current?.focus();
+        }}
+    >
+        {renderTitle()}
+
+        <View
+            style={[
+                contentContainerStyle,
+                stylesheet.content,
+                contentDynamicStyle
+            ]}
+        >
+            {renderIcon()}
+
+            {renderInput()}
+
+            {renderCleanButton()}
+            {renderHideTextIcon()}
+            {renderRightIcon()}
+
+            {renderOverlay()}
+        </View>
+
+        {renderHintText()}
+    </TouchableOpacity>;
+};
+export default forwardRef(NumericInput);

+ 278 - 0
src/components/numericInput/stylesheet.ts

@@ -0,0 +1,278 @@
+import {
+    type ViewStyle,
+    type TextStyle,
+    StyleSheet
+} from "react-native";
+import {
+    type NumericInputDynamicStyleType,
+    type NumericInputTypes,
+    type NumericInputType
+} from "./type";
+import type {
+    Mutable
+} from "../../types";
+
+export const NUMERIC_INPUT_TYPE_STYLES: Record<
+    NumericInputType,
+    NumericInputTypes
+> = {
+    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 getNumericInputType = ({
+    type
+}: {
+    type: NumericInputType;
+}) => {
+    const currentType = NUMERIC_INPUT_TYPE_STYLES[type];
+
+    return currentType;
+};
+
+const stylesheet = StyleSheet.create({
+    container: {
+        flexDirection: "column",
+        boxSizing: "border-box",
+        display: "flex"
+    },
+    content: {
+        boxSizing: "border-box",
+        flexDirection: "row",
+        borderStyle: "solid",
+        alignItems: "center",
+        position: "relative",
+        display: "flex"
+    },
+    input: {
+        backgroundColor: "transparent",
+        textAlignVertical: "center",
+        borderColor: "transparent",
+        paddingBottom: 0,
+        paddingRight: 0,
+        paddingLeft: 0,
+        borderWidth: 0,
+        paddingTop: 0,
+        minHeight: 20,
+        zIndex: 99,
+        flex: 1
+    },
+    cleanButton: {
+        justifyContent: "center",
+        alignItems: "center",
+        alignSelf: "center",
+        display: "flex",
+        zIndex: 99
+    },
+    hideTextIconContainer: {
+        justifyContent: "center",
+        alignItems: "center",
+        alignSelf: "center",
+        display: "flex",
+        zIndex: 99,
+        height: 18
+    },
+    titleContainer: {
+        flexDirection: "row",
+        alignItems: "center"
+    },
+    title: {
+    },
+    icon: {
+        justifyContent: "center",
+        alignContent: "center",
+        alignItems: "center",
+        display: "flex",
+        zIndex: 99
+    },
+    required: {
+    },
+    hintTextIcon: {
+    },
+    hintText: {
+        flexDirection: "row",
+        alignItems: "center",
+        display: "flex"
+    },
+    subTitle: {
+    },
+    overlay: {
+        position: "absolute",
+        zIndex: 98,
+        bottom: 0,
+        right: 0,
+        left: 0,
+        top: 0
+    },
+    rightIcon: {
+        justifyContent: "center",
+        alignItems: "center",
+        alignSelf: "center",
+        display: "flex",
+        zIndex: 99
+    }
+});
+
+export const useStyles = ({
+    spreadBehaviour,
+    inlineSpaces,
+    currentType,
+    typography,
+    isDisabled,
+    isFocused,
+    radiuses,
+    borders,
+    colors,
+    spaces,
+    type
+}: NumericInputDynamicStyleType) => {
+    const styleType = type === "danger" ? "error" : type === "question" ? "neutral" : type;
+
+    const styles = {
+        container: {
+        } as Mutable<ViewStyle>,
+        content: {
+            minWidth: (typography.labelLargeSize.fontSize * 2) + (spaces.spacingMd * 2),
+            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>,
+        input: {
+            ...typography.labelLargeSize,
+            color: colors.content.text[currentType.inputColor]
+        } as Mutable<TextStyle>,
+        cleanButton: {
+            marginLeft: spaces.spacingSm
+        } as Mutable<ViewStyle>,
+        hideTextIconContainer: {
+            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.alignSelf = spreadBehaviour;
+        styles.container.justifyContent = "center";
+        styles.container.flexShrink = 1;
+        styles.container.width = "100%";
+    }
+
+    if(isFocused && !isDisabled) {
+        if(type === "question" || type === "default") {
+            styles.content.borderColor = colors.content.border.emphasized;
+        } else {
+            styles.content.borderColor = colors.content.border[type];
+        }
+    }
+
+    return styles;
+};
+export default stylesheet;

+ 105 - 0
src/components/numericInput/type.ts

@@ -0,0 +1,105 @@
+import type {
+    ComponentRef
+} from "react";
+import {
+    type TextInputProps,
+    type StyleProp,
+    type ViewStyle,
+    type TextStyle,
+    TextInput
+} from "react-native";
+import {
+    type NCoreUIKitIcon
+} from "../../types";
+
+export type INumericInputRef = {
+    updateValue: (text: string) => void;
+    cleanText: () => void;
+    focus: () => void;
+    blur: () => void;
+};
+
+export type NumericInputDynamicStyleType = {
+    inlineSpaces: NCoreUIKit.ActivePalette["inlineSpaces"];
+    typography: NCoreUIKit.ActivePalette["typography"];
+    radiuses: NCoreUIKit.ActivePalette["radiuses"];
+    spreadBehaviour?: NumericInputSpreadBehaviour;
+    spaces: NCoreUIKit.ActivePalette["spaces"];
+    colors: NCoreUIKit.ActivePalette["colors"];
+    currentType: NumericInputTypes;
+    borders: NCoreUIKit.Borders;
+    type: NumericInputType;
+    isDisabled?: boolean;
+    isFocused: boolean;
+    title?: string;
+    icon?: boolean;
+};
+
+export type NumericInputTypes = {
+    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 NumericInputType = "default" | "danger" | "warning" | "question" | "success" | "info";
+
+export type NumericInputInstance = NonNullable<ComponentRef<typeof TextInput>>;
+
+export type NumericInputSpreadBehaviour = "baseline" | "stretch" | "free";
+
+export type NumericInputVariant = "text" | "hidden";
+
+interface INumericInputProps extends TextInputProps {
+    inputStyle?: StyleProp<TextStyle> | Array<StyleProp<TextStyle>>;
+    customTheme?: {
+        gapPropagation?: keyof NCoreUIKit.GapPropagationKey;
+        sharpness?: keyof NCoreUIKit.SharpnessKey;
+        paletteKey?: keyof NCoreUIKit.PaletteKey;
+        themeKey?: keyof NCoreUIKit.ThemeKey;
+    };
+    customLocalize?: {
+        activeLocale?: keyof NCoreUIKit.LocaleKey;
+    };
+    spreadBehaviour?: NumericInputSpreadBehaviour;
+    onChangeText?: (value: string) => void;
+    validation?: (text: string) => boolean;
+    isAutoKeyboardDismissOnBlur?: boolean;
+    hintTextContainerStyle?: ViewStyle;
+    contentContainerStyle?: ViewStyle;
+    isShowHideTextButton?: boolean;
+    hideTextIconStyle?: ViewStyle;
+    rightIconOnPress?: () => void;
+    hintTextIcon?: NCoreUIKitIcon;
+    variant?: NumericInputVariant;
+    isShowHintTextIcon?: boolean;
+    cleanIconStyle?: ViewStyle;
+    rightIconStyle?: ViewStyle;
+    rightIcon?: NCoreUIKitIcon;
+    isShowSubTitle?: boolean;
+    isCleanEnabled?: boolean;
+    iconOnPress?: () => void;
+    type?: NumericInputType;
+    initialValue?: string;
+    iconStyle?: ViewStyle;
+    icon?: NCoreUIKitIcon;
+    placeholder?: string;
+    isRequired?: boolean;
+    isDisabled?: boolean;
+    isOptional?: boolean;
+    onFocus?: () => void;
+    onBlur?: () => void;
+    hintText?: string;
+    subTitle?: string;
+    style?: ViewStyle;
+    title?: string;
+    id?: string;
+};
+export type {
+    INumericInputProps as default
+};

+ 343 - 0
src/components/timeSelector/index.tsx

@@ -0,0 +1,343 @@
+import {
+    TouchableOpacity,
+    View
+} from "react-native";
+import type ITimeSelectorProps from "./type";
+import type {
+    UpdateDateRangeType,
+    UpdateDateType
+} from "./type";
+import stylesheet, {
+    useStyles
+} from "./stylesheet";
+import {
+    NCoreUIKitLocalize,
+    NCoreUIKitTheme
+} from "../../core/hooks";
+import type INumericInputProps from "../numericInput/type";
+import {
+    Fragment
+} from "react/jsx-runtime";
+import NumericInput from "../numericInput";
+import Text from "../text";
+
+const TimeSelector = ({
+    isWorkWithSeconds = false,
+    setIsSheetContentReady,
+    selectMultipleObject,
+    selectObject,
+    pickerType,
+    dateRange,
+    maxDate,
+    minDate,
+    variant,
+    date
+}: ITimeSelectorProps) => {
+    const {
+        typography,
+        radiuses,
+        borders,
+        spaces,
+        colors
+    } = NCoreUIKitTheme.useContext();
+
+    const {
+        localize
+    } = NCoreUIKitLocalize.useContext();
+
+    const {
+        inputsContainer: inputsContainerDynamicStyle,
+        rangeContainer: rangeContainerDynamicStyle,
+        timeContainer: timeContainerDynamicStyle,
+        timeInput: timeInputDynamicStyle,
+        container: containerDynamicStyle,
+        colonText: colonTextDynamicStyle,
+        timeTitle: timeTitleDynamicStyle
+    } = useStyles({
+        pickerType,
+        typography,
+        radiuses,
+        borders,
+        colors,
+        spaces
+    });
+
+    const setDateRangeValue = ({
+        rangeType,
+        seconds,
+        minutes,
+        hours
+    }: UpdateDateRangeType) => {
+        if(!dateRange) {
+            return;
+        }
+
+        const newRangeDate = dateRange[rangeType] ? dateRange[rangeType] : new Date();
+
+        if(hours) {
+            newRangeDate?.setHours(hours);
+        }
+
+        if(minutes) {
+            newRangeDate?.setMinutes(minutes);
+        }
+
+        if(seconds) {
+            newRangeDate?.setSeconds(seconds);
+        }
+
+        const newMultipleObject = dateRange;
+        newMultipleObject[rangeType] = newRangeDate;
+
+        selectMultipleObject(newMultipleObject);
+    };
+
+    const setDateValue = ({
+        seconds,
+        minutes,
+        hours
+    }: UpdateDateType) => {
+        const newDate = date ? date : new Date();
+
+        if(hours) {
+            newDate?.setHours(hours);
+        }
+
+        if(minutes) {
+            newDate?.setMinutes(minutes);
+        }
+
+        if(seconds) {
+            newDate?.setSeconds(seconds);
+        }
+
+        selectObject(newDate);
+    };
+
+    const RenderTimeInput = ({
+        rangeType,
+        timeType,
+        ...props
+    }: INumericInputProps & {
+        timeType: "hours" | "minutes" | "seconds";
+        rangeType?: "start" | "end";
+    }) => {
+        return <TouchableOpacity
+            style={[
+                stylesheet.timeContainer,
+                timeContainerDynamicStyle
+            ]}
+        >
+            <NumericInput
+                {...props}
+                onChangeText={(text) => {
+                    if(variant === "range") {
+                        const updateTime: UpdateDateRangeType = {
+                            rangeType: rangeType as "start" | "end"
+                        };
+
+                        updateTime[timeType] = Number(text);
+
+                        setDateRangeValue(updateTime);
+                    } else {
+                        setDateValue({
+                            [timeType]: Number(text)
+                        });
+                    }
+                }}
+                inputStyle={[
+                    stylesheet.timeInput,
+                    timeInputDynamicStyle
+                ]}
+                keyboardType="numeric"
+                textAlign="center"
+                numberOfLines={1}
+                maxLength={2}
+            />
+        </TouchableOpacity>;
+    };
+
+    const renderTimeInputs = () => {
+        if(variant === "range") {
+            return <View
+                style={[
+                    stylesheet.rangeContainer,
+                    rangeContainerDynamicStyle
+                ]}
+            >
+                <Text
+                    variant="labelMediumSize"
+                    style={{
+                        ...stylesheet.timeTitle,
+                        ...timeTitleDynamicStyle
+                    }}
+                >
+                    {localize("start-time")}:
+                </Text>
+                <View
+                    style={[
+                        stylesheet.inputsContainer,
+                        inputsContainerDynamicStyle,
+                        {
+                            marginBottom: spaces.spacingMd
+                        }
+                    ]}
+                >
+                    <RenderTimeInput
+                        initialValue={dateRange?.start?.getHours().toString().padStart(2, "0")}
+                        rangeType="start"
+                        timeType="hours"
+                    />
+                    <Text
+                        variant="titleMediumSize"
+                        style={{
+                            ...stylesheet.colonText,
+                            ...colonTextDynamicStyle
+                        }}
+                    >
+                        :
+                    </Text>
+                    <RenderTimeInput
+                        initialValue={dateRange?.start?.getMinutes().toString().padStart(2, "0")}
+                        timeType="minutes"
+                        rangeType="start"
+                    />
+                    {
+                        isWorkWithSeconds ? <Fragment>
+                            <Text
+                                variant="titleMediumSize"
+                                style={{
+                                    ...stylesheet.colonText,
+                                    ...colonTextDynamicStyle
+                                }}
+                            >
+                                :
+                            </Text>
+                            <RenderTimeInput
+                                initialValue={dateRange?.start?.getSeconds().toString().padStart(2, "0")}
+                                timeType="seconds"
+                                rangeType="start"
+                            />
+                        </Fragment> : null
+                    }
+                </View>
+                <Text
+                    variant="labelMediumSize"
+                    style={{
+                        ...stylesheet.timeTitle,
+                        ...timeTitleDynamicStyle
+                    }}
+                >
+                    {localize("end-time")}:
+                </Text>
+                <View
+                    style={[
+                        stylesheet.inputsContainer,
+                        inputsContainerDynamicStyle
+                    ]}
+                >
+                    <RenderTimeInput
+                        initialValue={dateRange?.end?.getHours().toString().padStart(2, "0")}
+                        timeType="hours"
+                        rangeType="end"
+                    />
+                    <Text
+                        variant="titleMediumSize"
+                        style={{
+                            ...stylesheet.colonText,
+                            ...colonTextDynamicStyle
+                        }}
+                    >
+                        :
+                    </Text>
+                    <RenderTimeInput
+                        initialValue={dateRange?.end?.getMinutes().toString().padStart(2, "0")}
+                        timeType="minutes"
+                        rangeType="end"
+                    />
+                    {
+                        isWorkWithSeconds ? <Fragment>
+                            <Text
+                                variant="titleMediumSize"
+                                style={{
+                                    ...stylesheet.colonText,
+                                    ...colonTextDynamicStyle
+                                }}
+                            >
+                                :
+                            </Text>
+                            <RenderTimeInput
+                                initialValue={dateRange?.end?.getSeconds().toString().padStart(2, "0")}
+                                timeType="seconds"
+                                rangeType="end"
+                            />
+                        </Fragment> : null
+                    }
+                </View>
+            </View>;
+        }
+
+        return <View>
+            <Text
+                variant="labelMediumSize"
+                style={{
+                    ...stylesheet.timeTitle,
+                    ...timeTitleDynamicStyle
+                }}
+            >
+                {localize("end-time")}:
+            </Text>
+            <View
+                style={[
+                    stylesheet.inputsContainer,
+                    inputsContainerDynamicStyle
+                ]}
+            >
+                <RenderTimeInput
+                    initialValue={date?.getHours().toString().padStart(2, "0")}
+                    timeType="hours"
+                />
+                <Text
+                    variant="titleMediumSize"
+                    style={{
+                        ...stylesheet.colonText,
+                        ...colonTextDynamicStyle
+                    }}
+                >
+                    :
+                </Text>
+                <RenderTimeInput
+                    initialValue={date?.getMinutes().toString().padStart(2, "0")}
+                    timeType="minutes"
+                />
+                {
+                    isWorkWithSeconds ? <Fragment>
+                        <Text
+                            variant="titleMediumSize"
+                            style={{
+                                ...stylesheet.colonText,
+                                ...colonTextDynamicStyle
+                            }}
+                        >
+                            :
+                        </Text>
+                        <RenderTimeInput
+                            initialValue={date?.getSeconds().toString().padStart(2, "0")}
+                            timeType="seconds"
+                        />
+                    </Fragment> : null
+                }
+            </View>
+        </View>;
+    };
+
+    return <View
+        style={[
+            stylesheet.container,
+            containerDynamicStyle
+        ]}
+    >
+        {renderTimeInputs()}
+    </View>;
+};
+export default TimeSelector;

+ 67 - 0
src/components/timeSelector/stylesheet.ts

@@ -0,0 +1,67 @@
+import {
+    type TextStyle,
+    type ViewStyle,
+    StyleSheet
+} from "react-native";
+import type {
+    TimeSelectorDynamicStyle
+} from "./type";
+import type {
+    Mutable
+} from "../../types";
+
+const stylesheet = StyleSheet.create({
+    container: {
+        width: "100%"
+    },
+    rangeContainer: {
+        flexDirection: "column"
+    },
+    timeContainer: {
+    },
+    timeInput: {
+        textAlignVertical: "center",
+        verticalAlign: "middle",
+        textAlign: "center"
+    },
+    inputsContainer: {
+        justifyContent: "flex-start",
+        flexDirection: "row",
+        alignItems: "center"
+    },
+    colonText: {
+    },
+    timeTitle: {
+    }
+});
+
+export const useStyles = ({
+    typography,
+    radiuses,
+    spaces
+}: TimeSelectorDynamicStyle) => {
+    const styles = {
+        container: {
+            marginTop: spaces.spacingMd
+        } as Mutable<ViewStyle>,
+        rangeContainer: {
+        } as Mutable<ViewStyle>,
+        timeContainer: {
+            borderRadius: radiuses.actions
+        } as Mutable<ViewStyle>,
+        timeInput: {
+            ...typography.titleMediumSize
+        } as Mutable<TextStyle>,
+        inputsContainer: {
+            marginTop: spaces.spacingXs
+        } as Mutable<ViewStyle>,
+        colonText: {
+            marginHorizontal: spaces.spacingXs
+        } as Mutable<TextStyle>,
+        timeTitle: {
+        } as Mutable<TextStyle>
+    };
+
+    return styles;
+};
+export default stylesheet;

+ 47 - 0
src/components/timeSelector/type.ts

@@ -0,0 +1,47 @@
+import type {
+    SetStateAction,
+    Dispatch
+} from "react";
+import type {
+    DateTimePickerPickerType,
+    DateTimePickerDateRange,
+    DateTimePickerVariant
+} from "../dateTimePicker/type";
+
+export type TimeSelectorDynamicStyle = {
+    typography: NCoreUIKit.ActivePalette["typography"];
+    radiuses: NCoreUIKit.ActivePalette["radiuses"];
+    borders: NCoreUIKit.ActivePalette["borders"];
+    spaces: NCoreUIKit.ActivePalette["spaces"];
+    colors: NCoreUIKit.ActivePalette["colors"];
+    pickerType: DateTimePickerPickerType;
+};
+
+export type UpdateDateType = {
+    minutes?: number;
+    seconds?: number;
+    hours?: number;
+};
+
+export type UpdateDateRangeType = {
+    rangeType: "start" | "end";
+    minutes?: number;
+    seconds?: number;
+    hours?: number;
+};
+
+interface ITimeSelectorProps {
+    selectMultipleObject: (dateRange?: DateTimePickerDateRange) => void;
+    setIsSheetContentReady: Dispatch<SetStateAction<boolean>>;
+    pickerType: DateTimePickerPickerType;
+    selectObject: (date?: Date) => void;
+    dateRange?: DateTimePickerDateRange;
+    variant: DateTimePickerVariant;
+    isWorkWithSeconds?: boolean;
+    maxDate?: Date;
+    minDate?: Date;
+    date?: Date;
+};
+export type {
+    ITimeSelectorProps as default
+};

+ 1 - 0
src/index.tsx

@@ -19,6 +19,7 @@ export {
     DateTimePicker,
     PageContainer,
     TextAreaInput,
+    NumericInput,
     DateSelector,
     BottomSheet,
     SelectSheet,

+ 5 - 1
src/variants/locales/default.json

@@ -11,9 +11,11 @@
             "select-any-date": "Bir tarih seçin",
             "clean-selection": "Seçimi Temizle",
             "select-an-option": "Seçim yapın",
+            "start-time": "Başlangıç Zamanı",
             "clean-all": "Tümünü Temizle",
-            "select-all": "Tümünü Seç",
             "is-optional": "Opsiyonel",
+            "select-all": "Tümünü Seç",
+            "end-time": "Bitiş Zamanı",
             "cancel": "İptal",
             "search": "Ara",
             "ok": "Tamam"
@@ -100,9 +102,11 @@
             "select-an-option": "Select an option",
             "clean-selection": "Clean Selection",
             "select-any-date": "Select any date",
+            "start-time": "Start Time",
             "select-all": "Select All",
             "is-optional": "Optional",
             "clean-all": "Clean All",
+            "end-time": "End Time",
             "cancel": "Cancel",
             "search": "Search",
             "ok": "Okey"