Jelajahi Sumber

Feature: UserShortcut eklendi.

lfabl 1 Minggu lalu
induk
melakukan
5d1cd8e1d5

+ 10 - 5
src/components/bottomSheet/index.tsx

@@ -8,6 +8,7 @@ import {
 } from "react";
 import {
     type LayoutChangeEvent,
+    useWindowDimensions,
     PanResponder,
     ScrollView,
     Platform,
@@ -35,8 +36,6 @@ import type {
     IModalRef
 } from "../modal/type";
 import {
-    windowHeight,
-    windowWidth,
     webStyle,
     uuid
 } from "../../utils";
@@ -102,6 +101,11 @@ const BottomSheet: RefForwardingComponent<IBottomSheetRef, IBottomSheetProps> =
         setIsActive
     ] = useState(isActiveProp === undefined ? false : isActiveProp);
 
+    const {
+        height: windowHeight,
+        width: windowWidth
+    } = useWindowDimensions();
+
     const id = useRef(outID ? outID : uuid());
 
     const bottomSheetKey = useRef(customKey ? customKey : uuid());
@@ -1067,7 +1071,7 @@ const BottomSheet: RefForwardingComponent<IBottomSheetRef, IBottomSheetProps> =
                     scrollViewStyle,
                     webStyle({
                         maxWidth: windowWidth > 500 ? 500 : undefined,
-                        width: windowWidth > 500 ? 500 : undefined,
+                        width: windowWidth > 500 ? 500 : "100%",
                         alignSelf: "center"
                     })
                 ]}
@@ -1101,7 +1105,7 @@ const BottomSheet: RefForwardingComponent<IBottomSheetRef, IBottomSheetProps> =
                 style={[
                     webStyle({
                         maxWidth: windowWidth > 500 ? 500 : undefined,
-                        width: windowWidth > 500 ? 500 : undefined,
+                        width: windowWidth > 500 ? 500 : "100%",
                         alignSelf: "center"
                     })
                 ]}
@@ -1123,7 +1127,7 @@ const BottomSheet: RefForwardingComponent<IBottomSheetRef, IBottomSheetProps> =
                 style={[
                     webStyle({
                         maxWidth: windowWidth > 500 ? 500 : undefined,
-                        width: windowWidth > 500 ? 500 : undefined,
+                        width: windowWidth > 500 ? 500 : "100%",
                         alignSelf: "center"
                     })
                 ]}
@@ -1175,6 +1179,7 @@ const BottomSheet: RefForwardingComponent<IBottomSheetRef, IBottomSheetProps> =
                             ? colors.content.container[handleBackgroundColor]
                             : colors.content.container.default,
                         borderRadius: handleBorderRadius,
+                        width: windowWidth / 10,
                         height: handleHeight
                     }
                 ]}

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

@@ -1,9 +1,6 @@
 import {
     StyleSheet
 } from "react-native";
-import {
-    windowWidth
-} from "../../utils";
 
 const stylesheet = StyleSheet.create({
     container: {
@@ -22,7 +19,6 @@ const stylesheet = StyleSheet.create({
         left: 0
     },
     handle: {
-        width: windowWidth / 10
     }
 });
 export default stylesheet;

+ 8 - 0
src/components/index.ts

@@ -455,3 +455,11 @@ export {
 export type {
     default as ISystemToolbarProps
 } from "./systemToolbar/type";
+
+export {
+    default as UserShortcut
+} from "./userShortcut";
+
+export type {
+    default as IUserShortcutProps
+} from "./userShortcut/type";

+ 19 - 13
src/components/snackBar/index.tsx

@@ -7,6 +7,7 @@ import {
 } from "react";
 import {
     TouchableWithoutFeedback,
+    useWindowDimensions,
     PanResponder,
     Platform,
     Animated,
@@ -77,6 +78,10 @@ const SnackBar: FC<ISnackBarProps> = ({
         setIsMeasured
     ] = useState(false);
 
+    const {
+        width: windowWidth
+    } = useWindowDimensions();
+
     const contentHeight = useRef<number>(windowHeight);
 
     const transformAnim = useRef(new Animated.Value(-contentHeight.current + -top + -spaces.spacingSm)).current;
@@ -342,6 +347,20 @@ const SnackBar: FC<ISnackBarProps> = ({
     >
         <Animated.View
             {...(Platform.OS === "web" ? {} : panResponder.panHandlers)}
+            style={[
+                style,
+                stylesheet.container,
+                containerDynamicStyle,
+                {
+                    width: isFullWidth ? windowWidth : windowWidth - (spaces.spacingMd * 2),
+                    transform: [
+                        {
+                            translateY: transformAnim
+                        }
+                    ],
+                    opacity: opacityAnim
+                }
+            ]}
             onPointerDown={Platform.OS === "web" ? (evt) => {
                 const nativeEvt = evt as unknown as PointerEvent;
                 const startY = nativeEvt.clientY;
@@ -403,19 +422,6 @@ const SnackBar: FC<ISnackBarProps> = ({
 
                 setIsMeasured(true);
             }}
-            style={[
-                style,
-                stylesheet.container,
-                containerDynamicStyle,
-                {
-                    transform: [
-                        {
-                            translateY: transformAnim
-                        }
-                    ],
-                    opacity: opacityAnim
-                }
-            ]}
         >
             <TouchableWithoutFeedback
                 onPress={() => {

+ 0 - 5
src/components/snackBar/stylesheet.ts

@@ -12,9 +12,6 @@ import type {
 import type {
     Mutable
 } from "../../types";
-import {
-    windowWidth
-} from "../../utils";
 
 export const SNACK_BAR_TYPE_STYLES: Record<
     SnackBarType,
@@ -119,7 +116,6 @@ export const useStyles = ({
     const styles = {
         container: {
             backgroundColor: colors.content.container[currentType.containerColor],
-            width: windowWidth - (spaces.spacingMd * 2),
             top: safeAreaTop + spaces.spacingSm,
             borderRadius: radiuses.lg
         } as Mutable<ViewStyle>,
@@ -150,7 +146,6 @@ export const useStyles = ({
     };
 
     if(isFullWidth) {
-        styles.container.width = windowWidth;
         styles.container.borderRadius = 0;
     }
 

+ 6 - 0
src/components/toast/index.tsx

@@ -5,6 +5,7 @@ import {
 } from "react";
 import {
     TouchableWithoutFeedback,
+    useWindowDimensions,
     Animated,
     Easing,
     View
@@ -58,6 +59,10 @@ const Toast: FC<IToastProps> = ({
         bottom
     } = useSafeAreaInsets();
 
+    const {
+        width: windowWidth
+    } = useWindowDimensions();
+
     const TRANSFORM_DISTANCE = 75;
 
     const transformAnim = useRef(new Animated.Value(TRANSFORM_DISTANCE)).current;
@@ -252,6 +257,7 @@ const Toast: FC<IToastProps> = ({
                 stylesheet.container,
                 containerDynamicStyle,
                 {
+                    width: windowWidth - (spaces.spacingMd * 2),
                     transform: [
                         {
                             translateY: transformAnim

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

@@ -12,9 +12,6 @@ import type {
 import type {
     Mutable
 } from "../../types";
-import {
-    windowWidth
-} from "../../utils";
 
 export const TOAST_TYPE_STYLES: Record<
     ToastType,
@@ -117,7 +114,6 @@ export const useStyles = ({
     const styles = {
         container: {
             backgroundColor: colors.content.container[currentType.containerColor],
-            maxWidth: windowWidth - (spaces.spacingMd * 2),
             bottom: safeAreaBottom + 100,
             borderRadius: radiuses.full
         } as Mutable<ViewStyle>,

+ 17 - 9
src/components/tooltip/index.tsx

@@ -5,6 +5,7 @@ import {
     useRef
 } from "react";
 import {
+    useWindowDimensions,
     type ViewStyle,
     Pressable,
     Animated,
@@ -81,6 +82,10 @@ const Tooltip: FC<ITooltipProps> = ({
         setIsInternalVisible
     ] = useState(false);
 
+    const {
+        width: windowWidth
+    } = useWindowDimensions();
+
     const opacityAnim = useRef(new Animated.Value(0)).current;
     const wrapperRef = useRef<View>(null);
     const hoverTimeoutRef = useRef<NodeJS.Timeout | null>(null);
@@ -374,6 +379,8 @@ const Tooltip: FC<ITooltipProps> = ({
         return _style;
     };
 
+    const isFixed = sizing === "fixed";
+
     return <View
         onLayout={(e) => {
             const {
@@ -417,6 +424,16 @@ const Tooltip: FC<ITooltipProps> = ({
         {
             isShow || opacityAnim !== undefined ?
                 <Animated.View
+                    style={[
+                        stylesheet.container,
+                        containerDynamicStyle,
+                        getTooltipStyle(),
+                        {
+                            maxWidth: isFixed ? undefined : windowWidth - (spaces.spacingMd * 2),
+                            opacity: opacityAnim
+                        },
+                        containerStyle
+                    ]}
                     onLayout={(e) => {
                         const {
                             height,
@@ -434,15 +451,6 @@ const Tooltip: FC<ITooltipProps> = ({
                         });
                     }}
                     pointerEvents={isShow ? "auto" : "none"}
-                    style={[
-                        stylesheet.container,
-                        containerDynamicStyle,
-                        getTooltipStyle(),
-                        {
-                            opacity: opacityAnim
-                        },
-                        containerStyle
-                    ]}
                 >
                     <View
                         onPointerLeave={triggerMode === "hover" ? handleHoverOut : undefined}

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

@@ -13,9 +13,6 @@ import type {
 import type {
     Mutable
 } from "../../types";
-import {
-    windowWidth
-} from "../../utils";
 
 export const TOOLTIP_TYPE_STYLES: Record<
     TooltipType,
@@ -140,7 +137,6 @@ export const useStyles = ({
         container: {
             width: isFixed ? width : (Platform.OS === "web" ? "max-content" : undefined),
             backgroundColor: colors.content.container[currentType.containerColor],
-            maxWidth: isFixed ? undefined : windowWidth - (spaces.spacingMd * 2),
             height: isFixed ? height : undefined,
             minWidth: isFixed ? undefined : 50,
             borderRadius: radiuses.md

+ 168 - 0
src/components/userShortcut/index.tsx

@@ -0,0 +1,168 @@
+import {
+    Fragment
+} from "react";
+import {
+    TouchableOpacity,
+    View
+} from "react-native";
+import type IUserShortcutProps from "./type";
+import stylesheet, {
+    useStyles
+} from "./stylesheet";
+import {
+    NCoreUIKitTheme
+} from "../../core/hooks";
+import {
+    ChevronDown as ChevronDownIcon
+} from "lucide-react-native";
+import Avatar from "../avatar";
+import Text from "../text";
+
+const UserShortcut = ({
+    statusColor = "success",
+    avatarProps,
+    customTheme,
+    isDisabled,
+    statusText,
+    subTitle,
+    onPress,
+    badges,
+    title,
+    style
+}: IUserShortcutProps) => {
+    const {
+        colors,
+        spaces
+    } = NCoreUIKitTheme.useContext(customTheme);
+
+    const {
+        subTitleContainer: subTitleContainerDynamicStyle,
+        container: containerDynamicStyle
+    } = useStyles({
+        isDisabled,
+        spaces
+    });
+
+    const renderSubTitle = () => {
+        if(!subTitle && !statusText) {
+            return null;
+        }
+
+        return <View
+            style={[
+                stylesheet.subTitleContainer,
+                subTitleContainerDynamicStyle
+            ]}
+        >
+            {
+                statusText ?
+                    <Fragment>
+                        <Text
+                            style={stylesheet.statusText}
+                            variant="labelSmallSize"
+                            color={statusColor}
+                        >
+                            {statusText}
+                        </Text>
+                        <Text
+                            variant="labelSmallSize"
+                            style={stylesheet.dot}
+                            color="low"
+                        >
+                            •
+                        </Text>
+                    </Fragment>
+                :
+                    null
+            }
+            {
+                subTitle ?
+                    <Text
+                        style={stylesheet.subTitle}
+                        variant="labelSmallSize"
+                        ellipsizeMode="tail"
+                        numberOfLines={1}
+                        color="low"
+                    >
+                        {subTitle}
+                    </Text>
+                :
+                    null
+            }
+        </View>;
+    };
+
+    const renderContent = () => {
+        return <View
+            style={stylesheet.contentContainer}
+        >
+            <Text
+                style={stylesheet.title}
+                variant="labelLargeSize"
+                ellipsizeMode="tail"
+                numberOfLines={1}
+                color="high"
+            >
+                {title}
+            </Text>
+            {renderSubTitle()}
+        </View>;
+    };
+
+    const renderAction = () => {
+        return <View
+            style={stylesheet.actionContainer}
+        >
+            <ChevronDownIcon
+                color={colors.content.icon.default}
+                size={20}
+            />
+        </View>;
+    };
+
+    const renderBadges = () => {
+        if(!badges || badges.length === 0) {
+            return null;
+        }
+
+        return badges.map((badge, index) => {
+            return <View
+                style={[
+                    stylesheet.badgeContainer,
+                    {
+                        zIndex: index + 1
+                    }
+                ]}
+                pointerEvents="none"
+                key={index}
+            >
+                {badge}
+            </View>;
+        });
+    };
+
+    return <TouchableOpacity
+        onPress={isDisabled ? undefined : () => {
+            if(onPress) onPress();
+        }}
+        style={[
+            style,
+            stylesheet.container,
+            containerDynamicStyle
+        ]}
+        disabled={isDisabled}
+    >
+        <View
+            style={stylesheet.avatarContainer}
+        >
+            <Avatar
+                {...avatarProps}
+                size="medium"
+            />
+            {renderBadges()}
+        </View>
+        {renderContent()}
+        {renderAction()}
+    </TouchableOpacity>;
+};
+export default UserShortcut;

+ 80 - 0
src/components/userShortcut/stylesheet.ts

@@ -0,0 +1,80 @@
+import {
+    type ViewStyle,
+    StyleSheet
+} from "react-native";
+import type {
+    UserShortcutDynamicStyleType
+} from "./type";
+import type {
+    Mutable
+} from "../../types";
+
+const stylesheet = StyleSheet.create({
+    contentContainer: {
+        justifyContent: "center",
+        flexDirection: "column",
+        display: "flex",
+        flex: 1
+    },
+    actionContainer: {
+        justifyContent: "center",
+        alignItems: "center",
+        display: "flex"
+    },
+    avatarContainer: {
+        position: "relative",
+        display: "flex"
+    },
+    badgeContainer: {
+        position: "absolute",
+        bottom: 0,
+        right: 0,
+        left: 0,
+        top: 0
+    },
+    subTitleContainer: {
+        flexDirection: "row",
+        alignItems: "center",
+        display: "flex",
+        width: "100%"
+    },
+    container: {
+        flexDirection: "row",
+        alignItems: "center",
+        display: "flex",
+        width: "100%"
+    },
+    title: {
+        width: "100%"
+    },
+    statusText: {
+    },
+    subTitle: {
+        flex: 1
+    },
+    dot: {
+    }
+});
+
+export const useStyles = ({
+    isDisabled,
+    spaces
+}: UserShortcutDynamicStyleType) => {
+    const styles = {
+        container: {
+            paddingHorizontal: spaces.spacingSm,
+            paddingVertical: spaces.spacingXs,
+            gap: spaces.spacingSm
+        } as Mutable<ViewStyle>,
+        subTitleContainer: {
+            gap: spaces.spacingXs / 2
+        } as Mutable<ViewStyle>
+    };
+
+    if(isDisabled) {
+        styles.container.opacity = 0.33;
+    }
+
+    return styles;
+};
+export default stylesheet;

+ 34 - 0
src/components/userShortcut/type.ts

@@ -0,0 +1,34 @@
+import type {
+    ReactNode
+} from "react";
+import type {
+    StyleProp,
+    ViewStyle
+} from "react-native";
+import type IAvatarProps from "../avatar/type";
+
+export type UserShortcutDynamicStyleType = {
+    spaces: NCoreUIKit.ActivePalette["spaces"];
+    isDisabled?: boolean;
+};
+
+interface IUserShortcutProps {
+    customTheme?: {
+        gapPropagation?: keyof NCoreUIKit.GapPropagationKey;
+        sharpness?: keyof NCoreUIKit.SharpnessKey;
+        paletteKey?: keyof NCoreUIKit.PaletteKey;
+        themeKey?: keyof NCoreUIKit.ThemeKey;
+    };
+    style?: StyleProp<ViewStyle>[] | StyleProp<ViewStyle>;
+    statusColor?: keyof NCoreUIKit.TextContentColors;
+    avatarProps?: Omit<IAvatarProps, "size">;
+    badges?: ReactNode[];
+    isDisabled?: boolean;
+    onPress?: () => void;
+    statusText?: string;
+    subTitle?: string;
+    title: string;
+}
+export type {
+    IUserShortcutProps as default
+};

+ 2 - 0
src/index.tsx

@@ -29,6 +29,7 @@ export {
     TextAreaInput,
     ThemeSwitcher,
     SystemToolbar,
+    UserShortcut,
     WebScrollbar,
     DateSelector,
     EmbeddedMenu,
@@ -108,6 +109,7 @@ export type {
     ITextAreaInputProps,
     IThemeSwitcherProps,
     ISystemToolbarProps,
+    IUserShortcutProps,
     AvatarMeasuresKeys,
     DateTimePickerType,
     EnterMarkdownTypes,