Browse Source

Feature: Date Time Picker base structure completed.

lfabl 1 tháng trước cách đây
mục cha
commit
f85aebb22c

+ 8 - 8
example/package.json

@@ -11,21 +11,21 @@
     },
     "dependencies": {
         "@expo/metro-runtime": "~55.0.6",
-        "@react-navigation/elements": "^2.9.14",
-        "@react-navigation/native": "^7.2.2",
-        "@react-navigation/native-stack": "^7.14.11",
+        "@react-navigation/elements": "2.9.14",
+        "@react-navigation/native": "7.2.2",
+        "@react-navigation/native-stack": "7.14.11",
         "expo": "~55.0.4",
         "expo-font": "~55.0.4",
         "expo-status-bar": "~55.0.4",
-        "lucide-react-native": "^1.8.0",
+        "lucide-react-native": "1.8.0",
         "moment": "https://git.nibgat.space/nibgat-community/moment.git",
-        "ncore-context": "^1.0.5",
+        "ncore-context": "1.0.5",
         "react": "19.2.6",
         "react-dom": "19.2.6",
         "react-native": "0.83.2",
-        "react-native-safe-area-context": "^5.7.0",
-        "react-native-screens": "^4.24.0",
-        "react-native-svg": "^15.15.3",
+        "react-native-safe-area-context": "5.7.0",
+        "react-native-screens": "4.24.0",
+        "react-native-svg": "15.15.3",
         "react-native-web": "~0.21.0",
         "rrule": "https://git.nibgat.space/nibgat-community/rrule.git"
     },

+ 7 - 4
example/src/pages/home/index.tsx

@@ -16,6 +16,8 @@ import {
     NCoreUIKitDialog,
     NCoreUIKitToast,
     NCoreUIKitTheme,
+    DateTimePicker,
+    MarkdownViewer,
     PageContainer,
     TextAreaInput,
     BottomSheet,
@@ -27,9 +29,7 @@ import {
     Button,
     Dialog,
     Switch,
-    Text,
-    MarkdownViewer,
-    DateTimePicker
+    Text
 } from "ncore-ui-kit-mobile";
 import {
     useNavigation
@@ -367,7 +367,10 @@ const Home = () => {
         }}
     >
         <DateTimePicker
-            variant="range"
+            minDate={moment().subtract(2, "months").toDate()}
+            maxDate={moment().add(1, "months").toDate()}
+            isCleanEnabled={true}
+            variant="single"
             initialDateRange={{
                 start: moment().subtract(2, "days").startOf("day").toDate(),
                 end: moment().add(6, "days").endOf("day").toDate()

+ 40 - 14
src/components/bottomSheet/index.tsx

@@ -38,6 +38,7 @@ import {
 import Modal from "../modal";
 
 const BottomSheet: RefForwardingComponent<IBottomSheetRef, IBottomSheetProps> = ({
+    isContentReady: isContentReadyProp = true,
     renderHeader: RenderHeaderComponent,
     renderBottom: RenderBottomComponent,
     isForceFullScreenOnSwipe = false,
@@ -143,6 +144,8 @@ const BottomSheet: RefForwardingComponent<IBottomSheetRef, IBottomSheetProps> =
     const isSwipeCloseRef = useRef(isSwipeClose);
     const isCanSwipeRef = useRef(isCanSwipe);
 
+    const isContentReadyRef = useRef(isContentReadyProp);
+
     if(!isWorkAsFullScreen && !isCanFullScreenOnSwipe && snapPoint) {
         maxHeight.current = snapPoint;
     }
@@ -155,6 +158,9 @@ const BottomSheet: RefForwardingComponent<IBottomSheetRef, IBottomSheetProps> =
             },
             open: () => {
                 setIsActive(true);
+            },
+            setIsContentReady: (status) => {
+                isContentReadyRef.current = status;
             }
         }),
         []
@@ -176,6 +182,10 @@ const BottomSheet: RefForwardingComponent<IBottomSheetRef, IBottomSheetProps> =
         isWorkAsFullScreenRef.current = isWorkAsFullScreen;
     }, [isWorkAsFullScreen]);
 
+    useEffect(() => {
+        isContentReadyRef.current = isContentReadyProp;
+    }, [isContentReadyProp]);
+
     useEffect(() => {
         if (!isMeasured || contentHeight.current === -1) return;
 
@@ -186,7 +196,7 @@ const BottomSheet: RefForwardingComponent<IBottomSheetRef, IBottomSheetProps> =
         }
 
         if (isActive) {
-            openAnimation();
+            open();
         }
     }, [
         isActive,
@@ -196,7 +206,9 @@ const BottomSheet: RefForwardingComponent<IBottomSheetRef, IBottomSheetProps> =
     if(isActiveProp !== undefined) {
         useEffect(() => {
             setIsActive(isActiveProp);
-        }, [isActiveProp]);
+        }, [
+            isActiveProp
+        ]);
     }
 
     useEffect(() => {
@@ -279,7 +291,29 @@ const BottomSheet: RefForwardingComponent<IBottomSheetRef, IBottomSheetProps> =
         }
     };
 
-    const openAnimation = () => {
+    const openAnimation = (runIndex?: number) => {
+        if(isContentReadyRef.current) {
+            Animated.timing(animatedTranslateY, {
+                useNativeDriver: false,
+                duration: 300,
+                toValue: 0
+            }).start(({
+                finished
+            }) => {
+                if(finished) {
+                    if(onOpened) onOpened();
+                }
+            });
+        } else {
+            if(!runIndex || runIndex < 10) {
+                setTimeout(() => {
+                    openAnimation(runIndex ? runIndex + 1 : 1);
+                }, 100);
+            }
+        }
+    };
+
+    const open = () => {
         if (translateYValue.current < 0) {
             const safeValue = snapPoint ?? contentHeight.current;
             animatedTranslateY.setValue(safeValue);
@@ -294,17 +328,9 @@ const BottomSheet: RefForwardingComponent<IBottomSheetRef, IBottomSheetProps> =
 
         if(onOpen) onOpen();
 
-        Animated.timing(animatedTranslateY, {
-            useNativeDriver: false,
-            duration: 300,
-            toValue: 0
-        }).start(({
-            finished
-        }) => {
-            if(finished) {
-                if(onOpened) onOpened();
-            }
-        });
+        setTimeout(() => {
+            openAnimation();
+        }, 100);
     };
 
     const closeAnimation = (toValue?: number, _onClosed?: (props: {

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

@@ -9,6 +9,7 @@ import type {
 import type IModalProps from "../modal/type";
 
 export interface IBottomSheetRef {
+    setIsContentReady: (status: boolean) => void;
     close: (onClosed?: (props: {
         id: string;
     }) => void) => void;
@@ -32,6 +33,7 @@ interface IBottomSheetProps {
     scrollEndThreshold?: number;
     isCloseOnOverlay?: boolean;
     isWorkWithPortal?: boolean;
+    isContentReady?: boolean;
     onScrollEnd?: () => void;
     modalProps?: IModalProps;
     isSwipeClose?: boolean;

+ 40 - 4
src/components/dateSelector/index.tsx

@@ -1,7 +1,8 @@
 import {
+    useImperativeHandle,
+    useLayoutEffect,
     forwardRef,
     useEffect,
-    useImperativeHandle,
     useState,
     type Ref
 } from "react";
@@ -35,8 +36,11 @@ const DateSelector = ({
     localeBasedFirstDayOfWeek,
     dayOfWeekLength = "long",
     viewDate: viewDateProp,
+    setIsSheetContentReady,
     dateRange,
     dateRule,
+    maxDate,
+    minDate,
     setDate,
     variant,
     date
@@ -91,6 +95,12 @@ const DateSelector = ({
         date
     ]);
 
+    useLayoutEffect(() => {
+        if(monthDays) setIsSheetContentReady(true);
+    }, [
+        monthDays
+    ]);
+
     useEffect(() => {
         if(viewDateProp) {
             setViewDate(viewDateProp);
@@ -132,12 +142,23 @@ const DateSelector = ({
                 if(moment(currentDay).isBetween(dateRange.start, dateRange.end, "day", "[]")) isSelected = true;
             }
 
+            let isDisabled = false;
+
+            if(minDate && moment(currentDay).isBefore(minDate)) {
+                isDisabled = true;
+            }
+
+            if(maxDate && moment(currentDay).isAfter(maxDate)) {
+                isDisabled = true;
+            }
+
             return {
                 isCurrentMonth: currentDay.isSame(targetDate, "month"),
                 isToday: currentDay.isSame(moment(), "day"),
                 dayOfWeek: currentDay.weekday(),
                 dayNumber: currentDay.date(),
                 date: currentDay,
+                isDisabled,
                 isSelected
             };
         });
@@ -188,9 +209,21 @@ const DateSelector = ({
             } : null
         ];
 
+        let dayTitleColor: keyof NCoreUIKit.TextContentColors = "mid";
+
+        if(isPrevItemSelected && isNextItemSelected) {
+            dayTitleColor = "emphasized";
+        } else if(dayItem.isSelected) {
+            dayTitleColor = "onPrimary";
+        }
+
+        if(!dayItem.isCurrentMonth || dayItem.isDisabled) {
+            dayTitleColor = "disabled";
+        }
+
         return <TouchableOpacity
+            disabled={!dayItem.isCurrentMonth || dayItem.isDisabled}
             key={`day-${weekIndex}-${dayIndex}`}
-            disabled={!dayItem.isCurrentMonth}
             onPress={() => {
                 if(!dayItem.isCurrentMonth) {
                     return;
@@ -219,8 +252,8 @@ const DateSelector = ({
             ]}
         >
             <Text
-                color={isPrevItemSelected && isNextItemSelected ? "emphasized" : dayItem.isSelected ? "onPrimary" : dayItem.isCurrentMonth ? "mid" : "disabled"}
                 variant="labelLargeSize"
+                color={dayTitleColor}
             >
                 {dayItem.dayNumber}
             </Text>
@@ -228,7 +261,10 @@ const DateSelector = ({
                 style={[
                     stylesheet.todayIndicator,
                     selectionStyle,
-                    todayIndicatorDynamicStyle
+                    todayIndicatorDynamicStyle,
+                    dayItem.isSelected ? {
+                        borderColor: colors.content.border.subtle
+                    } : null
                 ]}
             /> : null}
         </TouchableOpacity>;

+ 1 - 1
src/components/dateSelector/stylesheet.ts

@@ -68,7 +68,7 @@ export const useStyles = ({
             padding: spaces.spacingSm
         } as Mutable<ViewStyle>,
         todayIndicator: {
-            borderColor: colors.content.container.emphasized,
+            borderColor: colors.content.border.default,
             borderBottomRightRadius: radiuses.full,
             borderBottomLeftRadius: radiuses.full,
             borderTopRightRadius: radiuses.full,

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

@@ -24,6 +24,7 @@ export type DateSelectorDynamicStyle = {
 
 export interface CalendarDay {
     isCurrentMonth: boolean;
+    isDisabled: boolean;
     isSelected: boolean;
     date: moment.Moment;
     dayNumber: number;
@@ -41,6 +42,7 @@ export type DateSelectInitialGeneratorType = {
 export type DayOfWeekLengthType = "short" | "very-short" | "long";
 
 interface IDateSelectorProps {
+    setIsSheetContentReady: Dispatch<SetStateAction<boolean>>;
     setDate: Dispatch<SetStateAction<Date | undefined>>;
     dayOfWeekLength?: DayOfWeekLengthType;
     localeBasedFirstDayOfWeek?: boolean;
@@ -49,6 +51,8 @@ interface IDateSelectorProps {
     variant: DateTimePickerVariant;
     dateRule?: RRule;
     viewDate?: Date;
+    maxDate?: Date;
+    minDate?: Date;
     date?: Date;
 };
 export type {

+ 4 - 0
src/components/dateTimePicker/index.tsx

@@ -113,6 +113,8 @@ const DateTimePicker = ({
     onChange,
     hintText,
     onCancel,
+    maxDate,
+    minDate,
     style,
     title,
     onOk
@@ -882,6 +884,8 @@ const DateTimePicker = ({
             isActive={isActive}
             variant={variant}
             setDate={setDate}
+            maxDate={maxDate}
+            minDate={minDate}
             cancel={cancel}
             clean={clean}
             title={title}

+ 3 - 1
src/components/dateTimePicker/stylesheet.ts

@@ -108,7 +108,9 @@ const stylesheet = StyleSheet.create({
         minWidth: 250
     },
     content: {
-
+        flexShrink: 1,
+        width: "100%",
+        flex: 1
     },
     contentText: {
         minHeight: 20

+ 15 - 10
src/components/dateTimeSheet/index.tsx

@@ -1,3 +1,6 @@
+import {
+    useState
+} from "react";
 import {
     View
 } from "react-native";
@@ -21,23 +24,14 @@ const DateTimeSheet = ({
     isShowTodayIndicator = true,
     dayOfWeekLength = "short",
     LoadingIconComponentProp,
-    selectMultipleObject,
     isWorkWithRealtime,
     isShowTools = true,
     dateTimePickerKey,
-    isMultipleSelect,
     bottomSheetProps,
-    isWorkWithRRule,
-    selectDateRule,
     bottomSheetRef,
     customLocalize,
-    setIsLoading,
-    selectObject,
-    setDateRange,
-    setDateRule,
     customTheme,
     setIsActive,
-    pickerType,
     dateRange,
     maxChoice,
     minChoice,
@@ -46,6 +40,8 @@ const DateTimeSheet = ({
     dateRule,
     setDate,
     variant,
+    maxDate,
+    minDate,
     cancel,
     clean,
     title,
@@ -60,6 +56,11 @@ const DateTimeSheet = ({
         localize
     } = NCoreUIKitLocalize.useContext(customLocalize);
 
+    const [
+        isSheetContentReady,
+        setIsSheetContentReady
+    ] = useState(false);
+
     const {
         loadingContainer: loadingContainerDynamicStyle,
         contentContainer: contentContainerDynamicStyle,
@@ -95,10 +96,13 @@ const DateTimeSheet = ({
         >
             <DateSelector
                 localeBasedFirstDayOfWeek={localeBasedFirstDayOfWeek}
+                setIsSheetContentReady={setIsSheetContentReady}
                 isShowTodayIndicator={isShowTodayIndicator}
                 dayOfWeekLength={dayOfWeekLength}
                 dateRange={dateRange}
                 dateRule={dateRule}
+                maxDate={maxDate}
+                minDate={minDate}
                 setDate={setDate}
                 variant={variant}
                 date={date}
@@ -204,6 +208,7 @@ const DateTimeSheet = ({
     return <BottomSheet
         {...bottomSheetProps}
         key={`${dateTimePickerKey}-bottomsheet`}
+        isContentReady={isSheetContentReady}
         customKey={dateTimePickerKey}
         ref={bottomSheetRef}
         isAutoHeight={true}
@@ -214,7 +219,7 @@ const DateTimeSheet = ({
         renderHeader={() => {
             return renderBottomSheetHeader();
         }}
-        onClose={() => {
+        onClosed={() => {
             setIsActive(false);
         }}
     >

+ 2 - 0
src/components/dateTimeSheet/type.ts

@@ -76,6 +76,8 @@ interface IDateTimeSheetProps {
     clean: () => void;
     isActive: boolean;
     dateRule?: RRule;
+    maxDate?: Date;
+    minDate?: Date;
     dateRange?: {
         start?: Date;
         end?: Date;

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

@@ -126,7 +126,6 @@ const stylesheet = StyleSheet.create({
         alignItems: "center"
     },
     title: {
-
     },
     icon: {
         justifyContent: "center",
@@ -143,7 +142,6 @@ const stylesheet = StyleSheet.create({
         alignItems: "center"
     },
     subTitle: {
-
     },
     overlay: {
         position: "absolute",
@@ -190,10 +188,8 @@ export const useStyles = ({
             borderWidth: borders.line
         } as Mutable<ViewStyle>,
         content: {
-
         } as Mutable<ViewStyle>,
         contentText: {
-
         } as Mutable<TextStyle>,
         cleanButton: {
             marginLeft: spaces.spacingSm

+ 1 - 1
src/components/selectSheet/index.tsx

@@ -362,7 +362,7 @@ function SelectSheet<T>({
         renderHeader={() => {
             return renderBottomSheetHeader();
         }}
-        onClose={() => {
+        onClosed={() => {
             setIsActive(false);
         }}
     >