浏览代码

Feature: UserShortcut component added.

lfabl 1 周之前
父节点
当前提交
28a2e2bba0

+ 11 - 0
example/src/index.tsx

@@ -15,6 +15,17 @@ import {
 import "moment/locale/tr";
 import moment from "moment";
 
+if (Platform.OS === "web" && typeof window !== "undefined") {
+    window.addEventListener("error", (e) => {
+        if (
+            e.message === "ResizeObserver loop limit exceeded" ||
+            e.message === "ResizeObserver loop completed with undelivered notifications."
+        ) {
+            e.stopImmediatePropagation();
+        }
+    });
+}
+
 let NCoreConfig: NCoreUIKitConfig = {
     projectThemes: defaultThemeJSON as NCoreUIKit.Palette,
     initialSelectedGapPropagation: "spacious",

+ 11 - 0
example/src/navigation/index.tsx

@@ -53,6 +53,7 @@ import DateSelectorPage from "../pages/components/dateSelector";
 import EmbeddedMenuPage from "../pages/components/embeddedMenu";
 import NumericInputPage from "../pages/components/numericInput";
 import TimeSelectorPage from "../pages/components/timeSelector";
+import UserShortcutPage from "../pages/components/userShortcut";
 import WebScrollbarPage from "../pages/components/webScrollbar";
 import YearSelectorPage from "../pages/components/yearSelector";
 import AvatarGroupPage from "../pages/components/avatarGroup";
@@ -328,6 +329,10 @@ const ComponentsNav = () => {
             component={TooltipPage}
             name="Tooltip"
         />
+        <ComponentsStack.Screen
+            component={UserShortcutPage}
+            name="UserShortcut"
+        />
         <ComponentsStack.Screen
             component={WebScrollbarPage}
             name="WebScrollbar"
@@ -685,6 +690,11 @@ const RootNav = () => {
                             redirectSub: "SiteLogo",
                             title: "SiteLogo"
                         },
+                        {
+                            redirectSub: "UserShortcut",
+                            redirectMain: "Components",
+                            title: "UserShortcut"
+                        },
                         {
                             redirectSub: "WebScrollbar",
                             redirectMain: "Components",
@@ -880,6 +890,7 @@ const Navigation = () => {
                             EmbeddedMenu: "embeddedmenu",
                             NumericInput: "numericinput",
                             TimeSelector: "timeselector",
+                            UserShortcut: "usershortcut",
                             WebScrollbar: "webscrollbar",
                             YearSelector: "yearselector",
                             AvatarGroup: "avatargroup",

+ 1 - 0
example/src/navigation/type.ts

@@ -33,6 +33,7 @@ export type ComponentsStackParamList = {
     TimeSelector: undefined;
     YearSelector: undefined;
     WebScrollbar: undefined;
+    UserShortcut: undefined;
     AvatarGroup: undefined;
     BottomSheet: undefined;
     RadioButton: undefined;

+ 330 - 0
example/src/pages/components/userShortcut/index.tsx

@@ -0,0 +1,330 @@
+import {
+    useLayoutEffect,
+    useState
+} from "react";
+import {
+    StyleSheet,
+    View
+} from "react-native";
+import {
+    type ActivityStatusType,
+    type IUserShortcutProps,
+    STATUS_CHEAT_SHEET,
+    NCoreUIKitTheme,
+    PageContainer,
+    UserShortcut,
+    CodeViewer,
+    SelectBox,
+    TextInput,
+    Switch,
+    Text
+} from "ncore-ui-kit";
+import {
+    useNavigation
+} from "@react-navigation/native";
+import type RootStackParamList from "../../../navigation/type";
+import type {
+    NativeStackNavigationProp
+} from "@react-navigation/native-stack";
+
+const STATUS_INDICATOR_TYPES_DATA = Object.keys(STATUS_CHEAT_SHEET).map((type) => ({
+    __title: type.charAt(0).toUpperCase() + type.slice(1),
+    __key: type
+}));
+
+const SPREAD_BEHAVIOURS: Array<NonNullable<IUserShortcutProps["spreadBehaviour"]>> = [
+    "baseline",
+    "stretch",
+    "free"
+];
+
+const SPREAD_BEHAVIOURS_DATA = SPREAD_BEHAVIOURS.map((type) => ({
+    __title: type ? type.charAt(0).toUpperCase() + type.slice(1) : "None",
+    __key: type
+}));
+
+const UserShortcutPage = () => {
+    const {
+        borders,
+        spaces,
+        colors
+    } = NCoreUIKitTheme.useContext();
+
+    const navigation = useNavigation<NativeStackNavigationProp<RootStackParamList>>();
+
+    const bgColorKeys = Object.keys(colors.content.container);
+
+    const [
+        statusIndicatorTypeIndex,
+        setStatusIndicatorTypeIndex
+    ] = useState(0);
+
+    const [
+        spreadBehaviourIndex,
+        setSpreadBehaviourIndex
+    ] = useState(0);
+
+    const [
+        backgroundColor,
+        setBackgroundColor
+    ] = useState<NonNullable<IUserShortcutProps["backgroundColor"]>>("mid");
+
+    const BACKGROUND_COLORS_DATA = [
+        "transparent",
+        ...bgColorKeys
+    ].map((color) => ({
+        __title: color.charAt(0).toUpperCase() + color.slice(1),
+        __key: color
+    }));
+
+    const [
+        statusText,
+        setStatusText
+    ] = useState("");
+
+    const [
+        isDisabled,
+        setIsDisabled
+    ] = useState(false);
+
+    const [
+        subTitle,
+        setSubTitle
+    ] = useState("Product Designer");
+
+    const [
+        title,
+        setTitle
+    ] = useState("Furkan Atakan BOZKURT");
+
+    useLayoutEffect(() => {
+        navigation.setOptions({
+            headerTitle: "UserShortcut",
+            headerShown: true
+        });
+    }, [
+        navigation
+    ]);
+
+    const statusIndicatorType = Object.keys(STATUS_CHEAT_SHEET)[statusIndicatorTypeIndex] as ActivityStatusType;
+    const spreadBehaviour = SPREAD_BEHAVIOURS[spreadBehaviourIndex];
+
+    const styles = StyleSheet.create({
+        controlsContainer: {
+            gap: spaces.spacingMd
+        },
+        rowContainer: {
+            flexDirection: "row",
+            alignItems: "center",
+            display: "flex"
+        }
+    });
+
+    return <PageContainer
+        isScrollable={true}
+    >
+        <View
+            style={{
+                paddingHorizontal: spaces.spacingMd,
+                paddingVertical: spaces.spacingLg
+            }}
+        >
+            <View
+                style={{
+                    borderColor: colors.content.container.mid,
+                    paddingHorizontal: spaces.spacingMd,
+                    paddingVertical: spaces.spacingLg,
+                    marginBottom: spaces.spacingXl,
+                    borderRadius: spaces.spacingMd,
+                    borderWidth: borders.line,
+                    justifyContent: "center",
+                    alignItems: "center"
+                }}
+            >
+                <UserShortcut
+                    actionButtons={[
+                        {
+                            onPress: () => console.log("Profile pressed"),
+                            title: "Profile"
+                        },
+                        {
+                            onPress: () => console.log("Settings pressed"),
+                            title: "Settings"
+                        }
+                    ]}
+                    avatarProps={{
+                        statusIndicatorType: statusIndicatorType,
+                        title
+                    }}
+                    onLogoutPress={() => {
+                        console.log("Logout pressed");
+                    }}
+                    spreadBehaviour={spreadBehaviour}
+                    backgroundColor={backgroundColor}
+                    onPress={() => {
+                        console.log("Card pressed");
+                    }}
+                    isDisabled={isDisabled}
+                    statusText={statusText}
+                    subTitle={subTitle}
+                    title={title}
+                />
+            </View>
+            <View
+                style={{
+                    marginBottom: spaces.spacingXl
+                }}
+            >
+                <Text
+                    style={{
+                        marginBottom: spaces.spacingSm
+                    }}
+                    variant="headlineSmallSize"
+                >
+                    Controls
+                </Text>
+                <View
+                    style={styles.controlsContainer}
+                >
+                    <View
+                        style={[
+                            styles.rowContainer,
+                            {
+                                gap: spaces.spacingMd
+                            }
+                        ]}
+                    >
+                        <SelectBox
+                            onChange={(selectedItems) => {
+                                if (selectedItems.length > 0) {
+                                    const index = Object.keys(STATUS_CHEAT_SHEET).indexOf(selectedItems[0]!.__key as ActivityStatusType);
+                                    if (index !== -1) {
+                                        setStatusIndicatorTypeIndex(index);
+                                    }
+                                }
+                            }}
+                            initialSelectedItems={
+                                STATUS_INDICATOR_TYPES_DATA[statusIndicatorTypeIndex]
+                                    ? [
+                                        STATUS_INDICATOR_TYPES_DATA[statusIndicatorTypeIndex]!
+                                    ]
+                                    : []
+                            }
+                            keyExtractor={(item) => item.__key as string}
+                            titleExtractor={(item) => item.__title}
+                            data={STATUS_INDICATOR_TYPES_DATA}
+                            title="Status Indicator"
+                            spreadBehaviour="free"
+                        />
+                        <SelectBox
+                            initialSelectedItems={
+                                SPREAD_BEHAVIOURS_DATA[spreadBehaviourIndex]
+                                    ? [
+                                        {
+                                            ...SPREAD_BEHAVIOURS_DATA[spreadBehaviourIndex]!,
+                                            __key: SPREAD_BEHAVIOURS_DATA[spreadBehaviourIndex]!.__key as NonNullable<IUserShortcutProps["spreadBehaviour"]>
+                                        }
+                                    ]
+                                    : []
+                            }
+                            onChange={(selectedItems) => {
+                                if (selectedItems.length > 0) {
+                                    const index = SPREAD_BEHAVIOURS.indexOf(selectedItems[0]!.__key as NonNullable<IUserShortcutProps["spreadBehaviour"]>);
+                                    if (index !== -1) setSpreadBehaviourIndex(index);
+                                }
+                            }}
+                            keyExtractor={(item) => item.__key as string}
+                            titleExtractor={(item) => item.__title}
+                            data={SPREAD_BEHAVIOURS_DATA}
+                            title="Spread Behaviour"
+                            spreadBehaviour="free"
+                        />
+                    </View>
+                    <View
+                        style={[
+                            styles.rowContainer,
+                            {
+                                gap: spaces.spacingMd
+                            }
+                        ]}
+                    >
+                        <SelectBox
+                            onChange={(selectedItems) => {
+                                if (selectedItems.length > 0) {
+                                    setBackgroundColor(selectedItems[0]!.__key as NonNullable<IUserShortcutProps["backgroundColor"]>);
+                                }
+                            }}
+                            initialSelectedItems={
+                                BACKGROUND_COLORS_DATA.filter(i => i.__key === backgroundColor).length > 0
+                                    ? [
+                                        {
+                                            ...BACKGROUND_COLORS_DATA.find(i => i.__key === backgroundColor)!,
+                                            __key: backgroundColor as string
+                                        }
+                                    ]
+                                    : []
+                            }
+                            keyExtractor={(item) => item.__key as string}
+                            titleExtractor={(item) => item.__title}
+                            data={BACKGROUND_COLORS_DATA}
+                            title="Background Color"
+                            spreadBehaviour="free"
+                        />
+                    </View>
+                    <TextInput
+                        spreadBehaviour="stretch"
+                        onChangeText={setTitle}
+                        title="Title"
+                        value={title}
+                    />
+                    <TextInput
+                        onChangeText={setSubTitle}
+                        spreadBehaviour="stretch"
+                        title="Sub Title"
+                        value={subTitle}
+                    />
+                    <TextInput
+                        onChangeText={setStatusText}
+                        spreadBehaviour="stretch"
+                        title="Status Text"
+                        value={statusText}
+                    />
+                    <View
+                        style={[
+                            styles.rowContainer,
+                            {
+                                gap: spaces.spacingMd
+                            }
+                        ]}
+                    >
+                        <Switch
+                            onPress={() => setIsDisabled(!isDisabled)}
+                            spreadBehaviour="baseline"
+                            isActive={isDisabled}
+                            title="Disabled"
+                        />
+                    </View>
+                </View>
+            </View>
+            <View
+                style={{
+                    marginBottom: spaces.spacingMd
+                }}
+            >
+                <Text
+                    style={{
+                        marginBottom: spaces.spacingSm
+                    }}
+                    variant="headlineSmallSize"
+                >
+                    Usage
+                </Text>
+                <CodeViewer
+                    code={"import {\n    UserShortcut\n} from \"ncore-ui-kit\";\n\n<UserShortcut\n    avatarProps={{\n        title: \"Murat Enes\",\n        statusIndicatorType: \"online\"\n    }}\n    onPress={() => {\n        // open bottom sheet\n    }}\n    subTitle=\"Product Designer\"\n    statusText=\"Online\"\n    title=\"Murat Enes\"\n/>"}
+                    language="tsx"
+                />
+            </View>
+        </View>
+    </PageContainer>;
+};
+export default UserShortcutPage;

+ 10 - 0
example/src/pages/components/userShortcut/stylesheet.ts

@@ -0,0 +1,10 @@
+import {
+    StyleSheet
+} from "react-native";
+
+const stylesheet = StyleSheet.create({
+    container: {
+        flex: 1
+    }
+});
+export default stylesheet;

+ 4 - 10
src/components/avatar/stylesheet.ts

@@ -4,7 +4,6 @@ import {
     StyleSheet
 } from "react-native";
 import type {
-    AvatarStatusIndicatorType,
     AvatarSizeConstantType,
     AvatarDynamicStyleType,
     AvatarMeasuresKeys,
@@ -14,6 +13,9 @@ import type {
 import type {
     Mutable
 } from "../../types";
+import {
+    STATUS_CHEAT_SHEET
+} from "../../utils";
 
 export const AVATAR_SIZES: Record<AvatarSizeType, AvatarMeasuresKeys> = {
     xLarge: {
@@ -79,14 +81,6 @@ export const getAvatarSize = ({
     };
 };
 
-export const AvatarStatusCheatsheet: Record<AvatarStatusIndicatorType, keyof NCoreUIKit.IconContentColors> = {
-    "offline": "default",
-    "online": "success",
-    "away": "warning",
-    "busy": "danger",
-    "idle": "info"
-};
-
 const stylesheet = StyleSheet.create({
     container: {
         position: "relative",
@@ -136,7 +130,7 @@ export const useStyles = ({
             width: currentSize.size
         } as Mutable<ImageStyle>,
         statusIndicator: {
-            backgroundColor: colors.content.icon[AvatarStatusCheatsheet[statusIndicatorType]],
+            backgroundColor: colors.content.icon[STATUS_CHEAT_SHEET[statusIndicatorType]],
             borderColor: colors.content.container[avatarBackgroundColor],
             borderRadius: currentSize.statusIndicatorSize / 2,
             height: currentSize.statusIndicatorSize,

+ 3 - 4
src/components/avatar/type.ts

@@ -8,6 +8,7 @@ import {
     type StyleProp
 } from "react-native";
 import type {
+    ActivityStatusType,
     NCoreUIKitIcon
 } from "../../types";
 
@@ -15,9 +16,9 @@ export type AvatarDynamicStyleType = {
     backgroundColor?: keyof NCoreUIKit.ContainerContentColors | "transparent";
     borderColor?: keyof NCoreUIKit.BorderContentColors | "transparent";
     avatarBackgroundColor: keyof NCoreUIKit.ContainerContentColors;
-    statusIndicatorType: AvatarStatusIndicatorType;
     borders: NCoreUIKit.ActivePalette["borders"];
     colors: NCoreUIKit.ActivePalette["colors"];
+    statusIndicatorType: ActivityStatusType;
     isShowStatusIndicatorSplicer?: boolean;
     image?: ComponentType<ImageProps>;
     currentSize: AvatarMeasures;
@@ -47,8 +48,6 @@ export type AvatarSizeConstantType = {
     size: AvatarSizeType;
 };
 
-export type AvatarStatusIndicatorType = "online" | "offline" | "busy" | "away" | "idle";
-
 export type AvatarTitleAbbreviationTypes = "first-last" | "first-next" | "every-first";
 
 export type AvatarSizeType = "xxSmall" | "xSmall" | "small" | "medium" | "large" | "xLarge" | "xxLarge" | "xxxLarge";
@@ -66,8 +65,8 @@ interface IAvatarProps {
     style?: StyleProp<TextStyle>[] | StyleProp<TextStyle>;
     titleAbbreviationType?: AvatarTitleAbbreviationTypes;
     iconColor?: keyof NCoreUIKit.ProjectColorPalette;
-    statusIndicatorType?: AvatarStatusIndicatorType;
     titleColor?: keyof NCoreUIKit.TextContentColors;
+    statusIndicatorType?: ActivityStatusType;
     isShowStatusIndicatorSplicer?: boolean;
     image?: ComponentType<ImageProps>;
     imageSource?: ImageURISource;

+ 16 - 2
src/components/bottomSheet/index.tsx

@@ -51,6 +51,7 @@ const BottomSheet: RefForwardingComponent<IBottomSheetRef, IBottomSheetProps> =
     isCanFullScreenOnSwipe = false,
     isWrapSafeAreaContext = true,
     backgroundColor = "default",
+    isWorkWithAnimation = false,
     isWorkAsFullScreen = false,
     scrollEndThreshold = 0.85,
     isCloseOnOverlay = true,
@@ -224,8 +225,17 @@ const BottomSheet: RefForwardingComponent<IBottomSheetRef, IBottomSheetProps> =
 
     if(isActiveProp !== undefined) {
         useEffect(() => {
-            setIsActive(isActiveProp);
+            setIsActive((prev) => {
+                if (isActiveProp === false && prev && isWorkWithAnimation) {
+                    closeAnimation();
+
+                    return prev;
+                }
+
+                return isActiveProp;
+            });
         }, [
+            isWorkWithAnimation,
             isActiveProp
         ]);
     }
@@ -249,9 +259,13 @@ const BottomSheet: RefForwardingComponent<IBottomSheetRef, IBottomSheetProps> =
 
     useEffect(() => {
         if (!isActive) {
+            const safeContentHeight = contentHeight.current === -1
+                ? containerHeightRef.current
+                : contentHeight.current;
+
             const newSnapValue = isWorkAsFullScreen
                 ? containerHeightRef.current
-                : (snapPoint ?? contentHeight.current);
+                : (snapPoint ?? safeContentHeight);
 
             animatedHeight.setValue(isAutoHeight ? 0 : newSnapValue);
             animatedTranslateY.setValue(newSnapValue);

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

@@ -34,6 +34,7 @@ interface IBottomSheetProps {
     isWrapSafeAreaContext?: boolean;
     renderHeader?: () => ReactNode;
     renderBottom?: () => ReactNode;
+    isWorkWithAnimation?: boolean;
     onOverlayPressed?: () => void;
     isWorkAsFullScreen?: boolean;
     scrollEndThreshold?: number;

+ 0 - 1
src/components/index.ts

@@ -295,7 +295,6 @@ export {
 
 export type {
     AvatarTitleAbbreviationTypes,
-    AvatarStatusIndicatorType,
     AvatarDynamicStyleType,
     AvatarSizeConstantType,
     default as IAvatarProps,

+ 163 - 52
src/components/userShortcut/index.tsx

@@ -1,4 +1,5 @@
 import {
+    useState,
     Fragment
 } from "react";
 import {
@@ -10,41 +11,84 @@ import stylesheet, {
     useStyles
 } from "./stylesheet";
 import {
+    NCoreUIKitLocalize,
     NCoreUIKitTheme
 } from "../../core/hooks";
 import {
-    ChevronDown as ChevronDownIcon
+    ChevronDown as ChevronDownIcon,
+    ChevronLeft as ChevronLeftIcon
 } from "lucide-react-native";
+import {
+    STATUS_CHEAT_SHEET
+} from "../../utils";
+import BottomSheet from "../bottomSheet";
 import Avatar from "../avatar";
+import Button from "../button";
 import Text from "../text";
 
 const UserShortcut = ({
-    statusColor = "success",
+    spreadBehaviour = "baseline",
+    backgroundColor = "mid",
+    actionButtons,
+    onLogoutPress,
     avatarProps,
     customTheme,
+    statusColor,
     isDisabled,
     statusText,
+    maxWidth,
     subTitle,
     onPress,
     badges,
     title,
     style
 }: IUserShortcutProps) => {
+    const [
+        isBottomSheetOpen,
+        setIsBottomSheetOpen
+    ] = useState(false);
+
     const {
-        colors,
-        spaces
+        radiuses,
+        spaces,
+        colors
     } = NCoreUIKitTheme.useContext(customTheme);
 
+    const {
+        localize
+    } = NCoreUIKitLocalize.useContext();
+
+    const resolvedStatusText = statusText || (
+        avatarProps?.statusIndicatorType ?
+            localize(`activity-status-${avatarProps.statusIndicatorType}` as keyof NCoreUIKit.Translation)
+        :
+            undefined
+    );
+
+    const resolvedStatusColor = statusColor || (
+        avatarProps?.statusIndicatorType ?
+            STATUS_CHEAT_SHEET[avatarProps.statusIndicatorType] as keyof NCoreUIKit.TextContentColors
+        :
+            "success"
+    );
+
     const {
         subTitleContainer: subTitleContainerDynamicStyle,
+        actionContainer: actionContainerDynamicStyle,
+        dotSeperator: dotSeperatorDynamicStyle,
         container: containerDynamicStyle
     } = useStyles({
+        spreadBehaviour,
+        backgroundColor,
         isDisabled,
+        maxWidth,
+        radiuses,
+        colors,
         spaces
     });
 
     const renderSubTitle = () => {
-        if(!subTitle && !statusText) {
+        if(!subTitle && !resolvedStatusText) {
             return null;
         }
 
@@ -55,40 +99,46 @@ const UserShortcut = ({
             ]}
         >
             {
-                statusText ?
+                resolvedStatusText ?
+                    <Text
+                        color={resolvedStatusColor}
+                        variant="labelSmallSize"
+                        ellipsizeMode="tail"
+                        numberOfLines={1}
+                        style={{
+                            flexShrink: 1
+                        }}
+                    >
+                        {resolvedStatusText}
+                    </Text>
+                :
+                    null
+            }
+            {
+                subTitle ?
                     <Fragment>
                         <Text
-                            style={stylesheet.statusText}
+                            style={{
+                                ...dotSeperatorDynamicStyle
+                            }}
                             variant="labelSmallSize"
-                            color={statusColor}
+                            color="low"
                         >
-                            {statusText}
+                            
                         </Text>
                         <Text
+                            style={stylesheet.subTitle}
                             variant="labelSmallSize"
-                            style={stylesheet.dot}
+                            ellipsizeMode="tail"
+                            numberOfLines={1}
                             color="low"
                         >
-                            •
+                            {subTitle}
                         </Text>
                     </Fragment>
                 :
                     null
             }
-            {
-                subTitle ?
-                    <Text
-                        style={stylesheet.subTitle}
-                        variant="labelSmallSize"
-                        ellipsizeMode="tail"
-                        numberOfLines={1}
-                        color="low"
-                    >
-                        {subTitle}
-                    </Text>
-                :
-                    null
-            }
         </View>;
     };
 
@@ -97,7 +147,6 @@ const UserShortcut = ({
             style={stylesheet.contentContainer}
         >
             <Text
-                style={stylesheet.title}
                 variant="labelLargeSize"
                 ellipsizeMode="tail"
                 numberOfLines={1}
@@ -111,11 +160,35 @@ const UserShortcut = ({
 
     const renderAction = () => {
         return <View
-            style={stylesheet.actionContainer}
+            style={[
+                stylesheet.actionContainer,
+                actionContainerDynamicStyle
+            ]}
         >
-            <ChevronDownIcon
-                color={colors.content.icon.default}
-                size={20}
+            <Button
+                icon={({
+                    color,
+                    size
+                }) => {
+                    if(isBottomSheetOpen) {
+                        return <ChevronDownIcon
+                            color={colors.content.icon[color]}
+                            size={size}
+                        />;
+                    }
+
+                    return <ChevronLeftIcon
+                        color={colors.content.icon[color]}
+                        size={size}
+                    />;
+                }}
+                onPress={() => {
+                    setIsBottomSheetOpen(!isBottomSheetOpen);
+                }}
+                isDisabled={isDisabled}
+                spreadBehaviour="free"
+                type="neutral"
+                size="small"
             />
         </View>;
     };
@@ -141,28 +214,66 @@ const UserShortcut = ({
         });
     };
 
-    return <TouchableOpacity
-        onPress={isDisabled ? undefined : () => {
-            if(onPress) onPress();
-        }}
-        style={[
-            style,
-            stylesheet.container,
-            containerDynamicStyle
-        ]}
-        disabled={isDisabled}
-    >
-        <View
-            style={stylesheet.avatarContainer}
+    const renderBottomSheet = () => {
+        return <BottomSheet
+            onClose={() => setIsBottomSheetOpen(false)}
+            isActive={isBottomSheetOpen}
+            isWorkWithAnimation={true}
         >
-            <Avatar
-                {...avatarProps}
-                size="medium"
-            />
-            {renderBadges()}
-        </View>
-        {renderContent()}
-        {renderAction()}
-    </TouchableOpacity>;
+            <View
+                style={{
+                    padding: spaces.spacingLg,
+                    gap: spaces.spacingMd
+                }}
+            >
+                {
+                    actionButtons?.map((buttonProps, index) => {
+                        return <Button
+                            spreadBehaviour="stretch"
+                            type="neutral"
+                            key={index}
+                            {...buttonProps}
+                        />;
+                    })
+                }
+                <Button
+                    title={localize("logout" as keyof NCoreUIKit.Translation)}
+                    onPress={() => {
+                        setIsBottomSheetOpen(false);
+                        if(onLogoutPress) onLogoutPress();
+                    }}
+                    spreadBehaviour="stretch"
+                    type="danger"
+                />
+            </View>
+        </BottomSheet>;
+    };
+
+    return <Fragment>
+        <TouchableOpacity
+            onPress={isDisabled ? undefined : () => {
+                if(onPress) onPress();
+            }}
+            style={[
+                style,
+                stylesheet.container,
+                containerDynamicStyle
+            ]}
+            disabled={isDisabled}
+        >
+            <View
+                style={stylesheet.avatarContainer}
+            >
+                <Avatar
+                    {...avatarProps}
+                    size="small"
+                />
+                {renderBadges()}
+            </View>
+            {renderContent()}
+            {renderAction()}
+        </TouchableOpacity>
+        {renderBottomSheet()}
+    </Fragment>;
 };
 export default UserShortcut;

+ 34 - 15
src/components/userShortcut/stylesheet.ts

@@ -1,5 +1,6 @@
 import {
     type ViewStyle,
+    type TextStyle,
     StyleSheet
 } from "react-native";
 import type {
@@ -13,7 +14,9 @@ const stylesheet = StyleSheet.create({
     contentContainer: {
         justifyContent: "center",
         flexDirection: "column",
+        overflow: "hidden",
         display: "flex",
+        minWidth: 0,
         flex: 1
     },
     actionContainer: {
@@ -35,36 +38,42 @@ const stylesheet = StyleSheet.create({
     subTitleContainer: {
         flexDirection: "row",
         alignItems: "center",
-        display: "flex",
-        width: "100%"
+        display: "flex"
     },
     container: {
         flexDirection: "row",
         alignItems: "center",
-        display: "flex",
-        width: "100%"
-    },
-    title: {
-        width: "100%"
-    },
-    statusText: {
+        display: "flex"
     },
     subTitle: {
-        flex: 1
-    },
-    dot: {
+        flex: 1,
+        flexShrink: 1
     }
 });
 
 export const useStyles = ({
+    backgroundColor,
+    spreadBehaviour,
     isDisabled,
+    maxWidth,
+    radiuses,
+    colors,
     spaces
 }: UserShortcutDynamicStyleType) => {
     const styles = {
         container: {
-            paddingHorizontal: spaces.spacingSm,
-            paddingVertical: spaces.spacingXs,
-            gap: spaces.spacingSm
+            backgroundColor: backgroundColor === "transparent" ? "transparent" : colors.content.container[backgroundColor],
+            paddingHorizontal: spaces.spacingMd,
+            paddingVertical: spaces.spacingMd,
+            borderRadius: radiuses.md,
+            gap: spaces.spacingSm,
+            maxWidth
+        } as Mutable<ViewStyle>,
+        dotSeperator: {
+            marginHorizontal: spaces.spacingXs
+        } as Mutable<TextStyle>,
+        actionContainer: {
+            marginLeft: spaces.spacingMd
         } as Mutable<ViewStyle>,
         subTitleContainer: {
             gap: spaces.spacingXs / 2
@@ -75,6 +84,16 @@ export const useStyles = ({
         styles.container.opacity = 0.33;
     }
 
+    if (spreadBehaviour === "baseline") {
+        styles.container.alignSelf = spreadBehaviour;
+        styles.container.width = "auto";
+    } else if (spreadBehaviour === "stretch") {
+        styles.container.alignSelf = spreadBehaviour;
+        styles.container.justifyContent = "center";
+        styles.container.flexShrink = 1;
+        styles.container.width = "100%";
+    }
+
     return styles;
 };
 export default stylesheet;

+ 15 - 1
src/components/userShortcut/type.ts

@@ -2,17 +2,27 @@ import type {
     ReactNode
 } from "react";
 import type {
+    DimensionValue,
     StyleProp,
     ViewStyle
 } from "react-native";
 import type IAvatarProps from "../avatar/type";
+import type IButtonProps from "../button/type";
+
+export type UserShortcutSpreadBehaviour = "baseline" | "stretch" | "free";
 
 export type UserShortcutDynamicStyleType = {
+    backgroundColor: keyof NCoreUIKit.ContainerContentColors | "transparent";
+    radiuses: NCoreUIKit.ActivePalette["radiuses"];
+    spreadBehaviour?: UserShortcutSpreadBehaviour;
+    colors: NCoreUIKit.ActivePalette["colors"];
     spaces: NCoreUIKit.ActivePalette["spaces"];
+    maxWidth?: DimensionValue;
     isDisabled?: boolean;
 };
 
 interface IUserShortcutProps {
+    backgroundColor?: keyof NCoreUIKit.ContainerContentColors | "transparent";
     customTheme?: {
         gapPropagation?: keyof NCoreUIKit.GapPropagationKey;
         sharpness?: keyof NCoreUIKit.SharpnessKey;
@@ -21,10 +31,14 @@ interface IUserShortcutProps {
     };
     style?: StyleProp<ViewStyle>[] | StyleProp<ViewStyle>;
     statusColor?: keyof NCoreUIKit.TextContentColors;
+    spreadBehaviour?: UserShortcutSpreadBehaviour;
     avatarProps?: Omit<IAvatarProps, "size">;
-    badges?: ReactNode[];
+    actionButtons?: IButtonProps[];
+    onLogoutPress?: () => void;
+    maxWidth?: DimensionValue;
     isDisabled?: boolean;
     onPress?: () => void;
+    badges?: ReactNode[];
     statusText?: string;
     subTitle?: string;
     title: string;

+ 2 - 1
src/index.tsx

@@ -78,7 +78,6 @@ export type {
     INotificationIndicatorProps,
     AvatarGroupDynamicStyleType,
     AvatarGroupSizeConstantType,
-    AvatarStatusIndicatorType,
     CheckBoxDynamicStyleType,
     CheckBoxTypeConstantType,
     DateTimePickerPickerType,
@@ -219,6 +218,7 @@ export type {
     PortalizedComponentProps,
     INCoreUIKitIconProps,
     GapPropagationType,
+    ActivityStatusType,
     NCoreUIKitConfig,
     RecursiveRecord,
     NCoreUIKitIcon,
@@ -243,6 +243,7 @@ export {
 
 export {
     androidTypographyFixer,
+    STATUS_CHEAT_SHEET,
     windowHeight,
     windowWidth,
     webStyle,

+ 2 - 0
src/types/index.ts

@@ -63,6 +63,8 @@ export type {
     ThemeType
 };
 
+export type ActivityStatusType = "online" | "offline" | "busy" | "away" | "idle";
+
 export type PureRNStyles = Omit<ViewStyle & TextStyle & ImageStyle, "position" | "cursor" | "filter">;
 
 export type PureWebStyles = Omit<CSSProperties, "position" | "cursor" | "filter">;

+ 9 - 0
src/utils/index.ts

@@ -4,6 +4,7 @@ import {
     Platform
 } from "react-native";
 import type {
+    ActivityStatusType,
     AllStyles
 } from "../types";
 
@@ -12,6 +13,14 @@ const dimensions = Dimensions.get("window");
 export const windowHeight = dimensions.height;
 export const windowWidth = dimensions.width;
 
+export const STATUS_CHEAT_SHEET: Record<ActivityStatusType, keyof NCoreUIKit.IconContentColors> = {
+    "online": "success",
+    "away": "warning",
+    "busy": "danger",
+    "offline": "low",
+    "idle": "info"
+};
+
 export const webStyle = <T extends AllStyles>(styles: T): ViewStyle => {
     return (Platform.OS === "web" ? styles : {}) as unknown as ViewStyle;
 };

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

@@ -20,7 +20,9 @@
             "copy-success-to-clipboard": "✅ Başarıyla kopyalandı.",
             "selected-options-with-count": "{{0}} seçim yapıldı",
             "copy-error-to-clipboard": "❌ Kopyalama başarısız.",
+            "activity-status-offline": "Çevrimdışı",
             "typography-headlineMediumSize": "H-M",
+            "activity-status-online": "Çevrimiçi",
             "typography-displayMediumSize": "D-M",
             "typography-headlineLargeSize": "H-L",
             "typography-headlineSmallSize": "H-S",
@@ -29,6 +31,7 @@
             "typography-displaySmallSize": "D-S",
             "clean-selection": "Seçimi Temizle",
             "typography-labelMediumSize": "L-M",
+            "activity-status-away": "Dışarıda",
             "typography-labelLargeSize": "L-L",
             "typography-labelSmallSize": "L-S",
             "typography-titleMediumSize": "H2",
@@ -36,15 +39,18 @@
             "typography-bodyMediumSize": "H5",
             "typography-titleLargeSize": "H1",
             "typography-titleSmallSize": "H3",
+            "activity-status-busy": "Meşgul",
             "start-time": "Başlangıç Zamanı",
             "typography-bodyLargeSize": "H4",
             "typography-bodySmallSize": "H6",
+            "activity-status-idle": "Boşta",
             "clean-all": "Tümünü Temizle",
             "components": "Bileşenler",
             "end-time": "Bitiş Zamanı",
             "is-optional": "Opsiyonel",
             "select-all": "Tümünü Seç",
             "go-to-today": "Bugün",
+            "logout": "Çıkış Yap",
             "preview": "Önizleme",
             "home": "Ana Sayfa",
             "monthly": "Aylık",
@@ -155,11 +161,13 @@
             "typography-displayMediumSize": "D-M",
             "typography-headlineLargeSize": "H-L",
             "typography-headlineSmallSize": "H-S",
+            "activity-status-offline": "Offline",
             "clean-selection": "Clean Selection",
             "select-any-date": "Select any date",
             "typography-displayLargeSize": "D-L",
             "typography-displaySmallSize": "D-S",
             "typography-labelMediumSize": "L-M",
+            "activity-status-online": "Online",
             "typography-labelLargeSize": "L-L",
             "typography-labelSmallSize": "L-S",
             "typography-titleMediumSize": "H2",
@@ -168,6 +176,9 @@
             "typography-titleSmallSize": "H3",
             "typography-bodyLargeSize": "H4",
             "typography-bodySmallSize": "H6",
+            "activity-status-away": "Away",
+            "activity-status-busy": "Busy",
+            "activity-status-idle": "Idle",
             "components": "Components",
             "select-all": "Select All",
             "start-time": "Start Time",
@@ -178,6 +189,7 @@
             "monthly": "Monthly",
             "preview": "Preview",
             "cancel": "Cancel",
+            "logout": "Logout",
             "search": "Search",
             "yearly": "Yearly",
             "daily": "Daily",