Selaa lähdekoodia

Merge branch 'release/1.1.0-alpha.19'

lfabl 1 viikko sitten
vanhempi
commit
576ffce0ba

+ 6 - 1
eslint.config.mjs

@@ -12,8 +12,13 @@ import js from "@eslint/js";
 export default tseslint.config(
     {
         ignores: [
-            "node_modules",
+            "**/node_modules/**",
+            "**/example/**",
+            "**/android/**",
+            "**/.yarn/**",
+            "**/.expo/**",
             "version.mjs",
+            "**/ios/**",
             ".history",
             "dist",
             "lib"

+ 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;

+ 9 - 12
example/src/pages/components/avatar/index.tsx

@@ -8,6 +8,7 @@ import {
 import stylesheet from "./stylesheet";
 import {
     NCoreUIKitLocalize,
+    STATUS_CHEAT_SHEET,
     NCoreUIKitTheme,
     PageContainer,
     CodeViewer,
@@ -26,22 +27,16 @@ import type {
     NativeStackNavigationProp
 } from "@react-navigation/native-stack";
 import type {
+    ActivityStatusType,
     IAvatarProps
 } from "ncore-ui-kit";
 
 const AVATAR_SIZES: Array<IAvatarProps["size"]> = [
-    "small",
     "medium",
-    "large",
     "xLarge",
-    "xSmall"
-];
-const AVATAR_STATUS_INDICATOR_TYPES: Array<IAvatarProps["statusIndicatorType"]> = [
-    "online",
-    "offline",
-    "busy",
-    "away",
-    "idle"
+    "xSmall",
+    "large",
+    "small"
 ];
 
 const AVATAR_SIZES_DATA = AVATAR_SIZES.map((item) => ({
@@ -49,11 +44,13 @@ const AVATAR_SIZES_DATA = AVATAR_SIZES.map((item) => ({
     __key: item as string
 }));
 
+const AVATAR_STATUS_INDICATOR_TYPES = Object.keys(STATUS_CHEAT_SHEET) as Array<ActivityStatusType>;
+
 const AVATAR_STATUS_INDICATOR_TYPES_DATA = AVATAR_STATUS_INDICATOR_TYPES.map(
     (item) => ({
         __title: item as string,
         __key: item as string
-    }),
+    })
 );
 
 const AvatarPage = () => {
@@ -239,7 +236,7 @@ const AvatarPage = () => {
                             const index =
                                     AVATAR_STATUS_INDICATOR_TYPES.indexOf(
                                         selectedItems[0]!
-                                            .__key as IAvatarProps["statusIndicatorType"],
+                                            .__key as ActivityStatusType,
                                     );
                             if (index !== -1)
                                 setStatusIndicatorIndex(index);

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

@@ -0,0 +1,486 @@
+import {
+    useLayoutEffect,
+    useState
+} from "react";
+import {
+    StyleSheet,
+    View
+} from "react-native";
+import {
+    NCoreUIKitCoreComplexProvider,
+    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 VARIANT_TYPES: Array<NonNullable<IUserShortcutProps["variant"]>> = [
+    "default",
+    "compact"
+];
+
+const VARIANT_TYPES_DATA = VARIANT_TYPES.map((type) => ({
+    __title: type.charAt(0).toUpperCase() + type.slice(1),
+    __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(Object.keys(STATUS_CHEAT_SHEET).findIndex(e => e === "online"));
+
+    const [
+        spreadBehaviourIndex,
+        setSpreadBehaviourIndex
+    ] = useState(0);
+
+    const [
+        variantIndex,
+        setVariantIndex
+    ] = 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");
+
+    const [
+        isShowLoginButtonWhenNoSession,
+        setIsShowLoginButtonWhenNoSession
+    ] = useState(true);
+
+    const [
+        isWorkWithCoreComplex,
+        setIsWorkWithCoreComplex
+    ] = useState(false);
+
+    const [
+        isShowRegisterButton,
+        setIsShowRegisterButton
+    ] = useState(false);
+
+    const [
+        isShowLogoutButton,
+        setIsShowLogoutButton
+    ] = useState(true);
+
+    const [
+        isShowArrowButton,
+        setIsShowArrowButton
+    ] = useState(true);
+
+    const [
+        isSessionActive,
+        setIsSessionActive
+    ] = useState(true);
+
+    useLayoutEffect(() => {
+        navigation.setOptions({
+            headerTitle: "UserShortcut",
+            headerShown: true
+        });
+    }, [
+        navigation
+    ]);
+
+    const statusIndicatorType = Object.keys(STATUS_CHEAT_SHEET)[statusIndicatorTypeIndex] as ActivityStatusType;
+    const spreadBehaviour = SPREAD_BEHAVIOURS[spreadBehaviourIndex];
+    const variant = VARIANT_TYPES[variantIndex];
+
+    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"
+                }}
+            >
+                <NCoreUIKitCoreComplexProvider
+                    privateKeyID="test"
+                    rsaPublicKey="test"
+                    privateKey="test"
+                    appID="test"
+                >
+                    <UserShortcut
+                        isShowLoginButtonWhenNoSession={isShowLoginButtonWhenNoSession}
+                        actionButtons={[
+                            {
+                                onPress: () => console.log("Profile pressed"),
+                                title: "Profile"
+                            },
+                            {
+                                onPress: () => console.log("Settings pressed"),
+                                title: "Settings"
+                            }
+                        ]}
+                        avatarProps={{
+                            statusIndicatorType: statusIndicatorType,
+                            title
+                        }}
+                        isWorkWithCoreComplex={isWorkWithCoreComplex}
+                        isShowRegisterButton={isShowRegisterButton}
+                        isShowLogoutButton={isShowLogoutButton}
+                        onLogoutPress={(response, error) => {
+                            console.log("Logout pressed", {
+                                response,
+                                error
+                            });
+                        }}
+                        isShowArrowButton={isShowArrowButton}
+                        onRegisterPress={() => {
+                            console.log("Register pressed");
+                        }}
+                        onLoginPress={() => {
+                            console.log("Login pressed");
+                        }}
+                        backgroundColor={backgroundColor}
+                        spreadBehaviour={spreadBehaviour}
+                        isSessionActive={isSessionActive}
+                        onPress={() => {
+                            console.log("Card pressed");
+                        }}
+                        isDisabled={isDisabled}
+                        statusText={statusText}
+                        subTitle={subTitle}
+                        variant={variant}
+                        title={title}
+                    />
+                </NCoreUIKitCoreComplexProvider>
+            </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) {
+                                    const index = VARIANT_TYPES.indexOf(selectedItems[0]!.__key as NonNullable<IUserShortcutProps["variant"]>);
+                                    if (index !== -1) setVariantIndex(index);
+                                }
+                            }}
+                            initialSelectedItems={
+                                VARIANT_TYPES_DATA[variantIndex]
+                                    ? [
+                                        {
+                                            ...VARIANT_TYPES_DATA[variantIndex]!,
+                                            __key: VARIANT_TYPES_DATA[variantIndex]!.__key as NonNullable<IUserShortcutProps["variant"]>
+                                        }
+                                    ]
+                                    : []
+                            }
+                            keyExtractor={(item) => item.__key as string}
+                            titleExtractor={(item) => item.__title}
+                            data={VARIANT_TYPES_DATA}
+                            spreadBehaviour="free"
+                            title="Variant"
+                        />
+                        <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"
+                        />
+                        <Switch
+                            onPress={() => setIsWorkWithCoreComplex(!isWorkWithCoreComplex)}
+                            isActive={isWorkWithCoreComplex}
+                            title="isWorkWithCoreComplex"
+                            spreadBehaviour="baseline"
+                        />
+                    </View>
+                    <View
+                        style={[
+                            styles.rowContainer,
+                            {
+                                gap: spaces.spacingMd
+                            }
+                        ]}
+                    >
+                        <Switch
+                            onPress={() => setIsShowLogoutButton(!isShowLogoutButton)}
+                            isActive={isShowLogoutButton}
+                            spreadBehaviour="baseline"
+                            title="isShowLogoutButton"
+                        />
+                        <Switch
+                            onPress={() => setIsShowArrowButton(!isShowArrowButton)}
+                            isActive={isShowArrowButton}
+                            spreadBehaviour="baseline"
+                            title="isShowArrowButton"
+                        />
+                    </View>
+                    <View
+                        style={[
+                            styles.rowContainer,
+                            {
+                                gap: spaces.spacingMd
+                            }
+                        ]}
+                    >
+                        <Switch
+                            onPress={() => setIsShowLoginButtonWhenNoSession(!isShowLoginButtonWhenNoSession)}
+                            isActive={isShowLoginButtonWhenNoSession}
+                            title="isShowLoginButtonWhenNoSession"
+                            spreadBehaviour="baseline"
+                        />
+                        <Switch
+                            onPress={() => setIsSessionActive(!isSessionActive)}
+                            spreadBehaviour="baseline"
+                            isActive={isSessionActive}
+                            title="isSessionActive"
+                        />
+                    </View>
+                    <View
+                        style={[
+                            styles.rowContainer,
+                            {
+                                gap: spaces.spacingMd
+                            }
+                        ]}
+                    >
+                        <Switch
+                            onPress={() => setIsShowRegisterButton(!isShowRegisterButton)}
+                            isActive={isShowRegisterButton}
+                            title="isShowRegisterButton"
+                            spreadBehaviour="baseline"
+                        />
+                    </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;

+ 3 - 6
example/src/pages/coreFeatures/defaultJSONs/index.tsx

@@ -65,12 +65,9 @@ const DefaultJSONs = () => {
     );
 
     const localeCode = JSON.stringify(
-        defaultLocaleJSON.map((localeObj) => {
-            return {
-                ...localeObj,
-                translations: Object.fromEntries(Object.entries(localeObj.translations).slice(0, 3))
-            };
-        }),
+        [
+            defaultLocaleJSON[0]
+        ],
         null,
         4
     );

+ 7 - 1
example/src/pages/home/stylesheet.ts

@@ -1,6 +1,9 @@
 import {
     StyleSheet
 } from "react-native";
+import {
+    webStyle
+} from "ncore-ui-kit";
 
 const stylesheet = StyleSheet.create({
     rowContainer: {
@@ -18,7 +21,10 @@ const stylesheet = StyleSheet.create({
     },
     contentContainer: {
         justifyContent: "center",
-        alignItems: "center"
+        alignItems: "center",
+        ...webStyle({
+            userSelect: "none"
+        })
     },
     previewContainer: {
         justifyContent: "center",

+ 12 - 0
example/web/webpack.config.js

@@ -119,6 +119,10 @@ module.exports = {
         ]
     },
     plugins: [
+        new webpack.ProvidePlugin({
+            process: "process/browser",
+            Buffer: ["buffer", "Buffer"],
+        }),
         new HtmlWebpackPlugin({
             template: path.resolve(appDirectory, "./public/index.html"),
             filename: "index.html"
@@ -164,6 +168,14 @@ module.exports = {
         })
     ],
     resolve: {
+        fallback: {
+            "crypto": require.resolve("crypto-browserify"),
+            "stream": require.resolve("stream-browserify"),
+            "process": require.resolve("process/browser"),
+            "vm": require.resolve("vm-browserify"),
+            "buffer": require.resolve("buffer/"),
+            "util": require.resolve("util/")
+        },
         symlinks: false,
         alias: {
             "react-native$": "react-native-web",

+ 9 - 1
package.json

@@ -1,6 +1,6 @@
 {
     "name": "ncore-ui-kit",
-    "version": "1.1.0-alpha.18",
+    "version": "1.1.0-alpha.19",
     "description": "NİBGAT® | NCore - UI Kit for React-Native Mobile Apps.",
     "main": "./lib/module/index.js",
     "types": "./lib/typescript/src/index.d.ts",
@@ -106,9 +106,11 @@
         "babel-plugin-module-resolver": "5.0.3",
         "babel-plugin-react-native-web": "0.21.2",
         "babel-preset-expo": "56.0.15",
+        "buffer": "^6.0.3",
         "copy-webpack-plugin": "11.0.0",
         "copyfiles": "2.4.1",
         "cross-env": "10.1.0",
+        "crypto-browserify": "^3.12.1",
         "del-cli": "6.0.0",
         "eslint": "9.37.0",
         "eslint-import-resolver-typescript": "4.4.4",
@@ -127,6 +129,7 @@
         "html-webpack-plugin": "5.6.7",
         "jest": "29.6.3",
         "jsonc-eslint-parser": "2.4.1",
+        "process": "^0.11.10",
         "react": "19.2.3",
         "react-dom": "19.2.3",
         "react-native": "0.85.3",
@@ -136,16 +139,20 @@
         "react-native-web": "0.21.2",
         "react-test-renderer": "19.2.3",
         "release-it": "19.0.4",
+        "stream-browserify": "^3.0.0",
         "turbo": "2.5.6",
         "typescript": "5.9.3",
         "typescript-eslint": "8.46.1",
         "url-loader": "4.1.1",
+        "util": "^0.12.5",
+        "vm-browserify": "^1.1.2",
         "webpack": "5.107.1",
         "webpack-cli": "7.0.2",
         "webpack-dev-server": "5.2.4"
     },
     "peerDependencies": {
         "@react-native-clipboard/clipboard": ">= 1.16.3",
+        "core-complex-client": ">= 1.0.19",
         "lucide-react-native": ">= 1.16.0",
         "moment": "*",
         "ncore-context": ">= 1.0.7",
@@ -208,6 +215,7 @@
     },
     "dependencies": {
         "@react-native-clipboard/clipboard": "1.16.3",
+        "core-complex-client": "1.0.19",
         "lucide-react-native": "1.16.0",
         "moment": "https://git.nibgat.space/nibgat-community/moment.git",
         "ncore-context": "1.0.8",

+ 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].icon],
             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;

+ 26 - 7
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";
@@ -52,6 +51,7 @@ const BottomSheet: RefForwardingComponent<IBottomSheetRef, IBottomSheetProps> =
     isCanFullScreenOnSwipe = false,
     isWrapSafeAreaContext = true,
     backgroundColor = "default",
+    isWorkWithAnimation = false,
     isWorkAsFullScreen = false,
     scrollEndThreshold = 0.85,
     isCloseOnOverlay = true,
@@ -102,6 +102,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());
@@ -220,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
         ]);
     }
@@ -245,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);
@@ -1067,7 +1085,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 +1119,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 +1141,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 +1193,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;

+ 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;

+ 8 - 1
src/components/index.ts

@@ -295,7 +295,6 @@ export {
 
 export type {
     AvatarTitleAbbreviationTypes,
-    AvatarStatusIndicatorType,
     AvatarDynamicStyleType,
     AvatarSizeConstantType,
     default as IAvatarProps,
@@ -455,3 +454,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

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

@@ -0,0 +1,421 @@
+import {
+    useState,
+    Fragment
+} from "react";
+import {
+    TouchableOpacity,
+    View
+} from "react-native";
+import type IUserShortcutProps from "./type";
+import stylesheet, {
+    useStyles
+} from "./stylesheet";
+import {
+    NCoreUIKitLocalize,
+    NCoreUIKitTheme
+} from "../../core/hooks";
+import {
+    useContext as useCoreComplexContext
+} from "../../core/contexts/coreComplex";
+import {
+    ChevronDown as ChevronDownIcon,
+    ChevronLeft as ChevronLeftIcon,
+    UserPlus as UserPlusIcon,
+    LogIn as LogInIcon
+} 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 = ({
+    isShowLoginButtonWhenNoSession = true,
+    isShowRegisterButton = false,
+    isWorkWithCoreComplex = true,
+    spreadBehaviour = "baseline",
+    isShowLogoutButton = true,
+    isShowArrowButton = true,
+    backgroundColor = "mid",
+    isSessionActive = true,
+    variant = "default",
+    onRegisterPress,
+    actionButtons,
+    onLogoutPress,
+    onLoginPress,
+    avatarProps,
+    customTheme,
+    statusColor,
+    isDisabled,
+    statusText,
+    maxWidth,
+    subTitle,
+    onPress,
+    badges,
+    title,
+    style
+}: IUserShortcutProps) => {
+    const [
+        isBottomSheetOpen,
+        setIsBottomSheetOpen
+    ] = useState(false);
+
+    const {
+        client: coreComplexClient
+    } = useCoreComplexContext();
+
+    const {
+        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].text as keyof NCoreUIKit.TextContentColors
+        :
+            "success"
+    );
+
+    const resolvedIsSessionActive = isWorkWithCoreComplex ?
+        !!coreComplexClient?.variables?.accessToken
+    :
+        isSessionActive;
+
+    const isNoSession = !resolvedIsSessionActive;
+
+    const {
+        subTitleContainer: subTitleContainerDynamicStyle,
+        actionContainer: actionContainerDynamicStyle,
+        dotSeperator: dotSeperatorDynamicStyle,
+        container: containerDynamicStyle
+    } = useStyles({
+        spreadBehaviour,
+        backgroundColor,
+        isDisabled,
+        maxWidth,
+        radiuses,
+        colors,
+        spaces
+    });
+
+    const renderSubTitle = () => {
+        if(isNoSession || (!subTitle && !resolvedStatusText)) {
+            return null;
+        }
+
+        return <View
+            style={[
+                stylesheet.subTitleContainer,
+                subTitleContainerDynamicStyle
+            ]}
+        >
+            {
+                resolvedStatusText ?
+                    <Text
+                        color={resolvedStatusColor}
+                        variant="labelSmallSize"
+                        ellipsizeMode="tail"
+                        numberOfLines={1}
+                        style={{
+                            flexShrink: 1
+                        }}
+                    >
+                        {resolvedStatusText}
+                    </Text>
+                :
+                    null
+            }
+            {
+                subTitle ?
+                    <Fragment>
+                        <Text
+                            style={{
+                                ...dotSeperatorDynamicStyle
+                            }}
+                            variant="labelSmallSize"
+                            color="low"
+                        >
+                            •
+                        </Text>
+                        <Text
+                            style={stylesheet.subTitle}
+                            variant="labelSmallSize"
+                            ellipsizeMode="tail"
+                            numberOfLines={1}
+                            color="low"
+                        >
+                            {subTitle}
+                        </Text>
+                    </Fragment>
+                :
+                    null
+            }
+        </View>;
+    };
+
+    const renderContent = () => {
+        if(isNoSession || variant === "compact") {
+            return null;
+        }
+
+        return <View
+            style={stylesheet.contentContainer}
+        >
+            <Text
+                variant="labelLargeSize"
+                ellipsizeMode="tail"
+                numberOfLines={1}
+                color="high"
+            >
+                {title}
+            </Text>
+            {renderSubTitle()}
+        </View>;
+    };
+
+    const renderAction = () => {
+        if(variant === "compact") {
+            return null;
+        }
+
+        if(isNoSession) {
+            if(!isShowLoginButtonWhenNoSession && !isShowRegisterButton) {
+                return null;
+            }
+
+            return <View
+                style={[
+                    stylesheet.actionContainer,
+                    actionContainerDynamicStyle,
+                    {
+                        marginLeft: spaces.spacingXs,
+                        gap: spaces.spacingMd,
+                        flexDirection: "row"
+                    }
+                ]}
+            >
+                {
+                    isShowLoginButtonWhenNoSession ?
+                        <Button
+                            icon={({
+                                color,
+                                size
+                            }) => {
+                                return <LogInIcon
+                                    color={colors.content.icon[color]}
+                                    size={size + 2}
+                                />;
+                            }}
+                            onPress={() => onLoginPress?.()}
+                            title={localize("login")}
+                            spreadBehaviour="free"
+                            type="neutral"
+                            size="small"
+                        />
+                    :
+                        null
+                }
+                {
+                    isShowRegisterButton ?
+                        <Button
+                            icon={({
+                                color,
+                                size
+                            }) => {
+                                return <UserPlusIcon
+                                    color={colors.content.icon[color]}
+                                    size={size + 2}
+                                />;
+                            }}
+                            onPress={() => onRegisterPress?.()}
+                            title={localize("register")}
+                            spreadBehaviour="free"
+                            type="neutral"
+                            size="small"
+                        />
+                    :
+                        null
+                }
+            </View>;
+        }
+
+        if(!isShowArrowButton) {
+            return null;
+        }
+
+        return <View
+            style={[
+                stylesheet.actionContainer,
+                actionContainerDynamicStyle
+            ]}
+        >
+            <Button
+                icon={({
+                    color,
+                    size
+                }) => {
+                    if(isBottomSheetOpen) {
+                        return <ChevronDownIcon
+                            color={colors.content.icon[color]}
+                            size={size + 5}
+                        />;
+                    }
+
+                    return <ChevronLeftIcon
+                        color={colors.content.icon[color]}
+                        size={size + 5}
+                    />;
+                }}
+                onPress={() => {
+                    setIsBottomSheetOpen(!isBottomSheetOpen);
+                }}
+                isDisabled={isDisabled}
+                spreadBehaviour="free"
+                type="neutral"
+                size="small"
+            />
+        </View>;
+    };
+
+    const renderBadges = () => {
+        if(isNoSession || !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>;
+        });
+    };
+
+    const renderBottomSheet = () => {
+        if(isNoSession || !isShowArrowButton) {
+            return null;
+        }
+
+        return <BottomSheet
+            onClose={() => setIsBottomSheetOpen(false)}
+            isActive={isBottomSheetOpen}
+            isWorkWithAnimation={true}
+        >
+            <View
+                style={{
+                    padding: spaces.spacingLg,
+                    gap: spaces.spacingMd
+                }}
+            >
+                {
+                    actionButtons?.map((buttonProps, index) => {
+                        return <Button
+                            spreadBehaviour="stretch"
+                            type="neutral"
+                            key={index}
+                            {...buttonProps}
+                        />;
+                    })
+                }
+                {
+                    isShowLogoutButton ?
+                        <Button
+                            title={localize("logout" as keyof NCoreUIKit.Translation)}
+                            onPress={async () => {
+                                setIsBottomSheetOpen(false);
+
+                                let response;
+                                let error;
+
+                                if(isWorkWithCoreComplex && coreComplexClient) {
+                                    try {
+                                        response = await (
+                                            coreComplexClient.NAuth as unknown as {
+                                                logout: () => Promise<unknown>;
+                                            }
+                                        ).logout();
+                                    } catch(e) {
+                                        error = e;
+                                    }
+                                }
+
+                                if(onLogoutPress) {
+                                    onLogoutPress(response, error);
+                                }
+                            }}
+                            spreadBehaviour="stretch"
+                            type="danger"
+                        />
+                    :
+                        null
+                }
+            </View>
+        </BottomSheet>;
+    };
+
+    return <Fragment>
+        <TouchableOpacity
+            activeOpacity={isNoSession && (isShowLoginButtonWhenNoSession || isShowRegisterButton) ? 1 : 0.2}
+            onPress={isDisabled ? undefined : () => {
+                if(isNoSession && (isShowLoginButtonWhenNoSession || isShowRegisterButton)) {
+                    return;
+                }
+
+                if(onPress) onPress();
+
+                if(variant === "compact" && isShowArrowButton && !isNoSession) {
+                    setIsBottomSheetOpen(!isBottomSheetOpen);
+                }
+            }}
+            style={[
+                style,
+                stylesheet.container,
+                containerDynamicStyle
+            ]}
+            disabled={isDisabled}
+        >
+            <View
+                style={stylesheet.avatarContainer}
+            >
+                <Avatar
+                    {...(isNoSession ? {
+                        ...avatarProps,
+                        isStatusIndicator: false,
+                        title: undefined,
+                        url: undefined
+                    } : {
+                        ...avatarProps,
+                        isStatusIndicator: avatarProps?.isStatusIndicator ?? !!resolvedStatusText
+                    })}
+                    size="medium"
+                />
+                {renderBadges()}
+            </View>
+            {renderContent()}
+            {renderAction()}
+        </TouchableOpacity>
+        {renderBottomSheet()}
+    </Fragment>;
+};
+export default UserShortcut;

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

@@ -0,0 +1,99 @@
+import {
+    type ViewStyle,
+    type TextStyle,
+    StyleSheet
+} from "react-native";
+import type {
+    UserShortcutDynamicStyleType
+} from "./type";
+import type {
+    Mutable
+} from "../../types";
+
+const stylesheet = StyleSheet.create({
+    contentContainer: {
+        justifyContent: "center",
+        flexDirection: "column",
+        overflow: "hidden",
+        display: "flex",
+        minWidth: 0,
+        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"
+    },
+    container: {
+        flexDirection: "row",
+        alignItems: "center",
+        display: "flex"
+    },
+    subTitle: {
+        flex: 1,
+        flexShrink: 1
+    }
+});
+
+export const useStyles = ({
+    backgroundColor,
+    spreadBehaviour,
+    isDisabled,
+    maxWidth,
+    radiuses,
+    colors,
+    spaces
+}: UserShortcutDynamicStyleType) => {
+    const styles = {
+        container: {
+            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
+        } as Mutable<ViewStyle>
+    };
+
+    if(isDisabled) {
+        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;

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

@@ -0,0 +1,57 @@
+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";
+    onLogoutPress?: (response?: unknown, error?: unknown) => void;
+    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;
+    spreadBehaviour?: UserShortcutSpreadBehaviour;
+    isShowLoginButtonWhenNoSession?: boolean;
+    avatarProps?: Omit<IAvatarProps, "size">;
+    variant?: "default" | "compact";
+    isWorkWithCoreComplex?: boolean;
+    isShowRegisterButton?: boolean;
+    actionButtons?: IButtonProps[];
+    onRegisterPress?: () => void;
+    isShowLogoutButton?: boolean;
+    isShowArrowButton?: boolean;
+    onLoginPress?: () => void;
+    maxWidth?: DimensionValue;
+    isSessionActive?: boolean;
+    isDisabled?: boolean;
+    onPress?: () => void;
+    badges?: ReactNode[];
+    statusText?: string;
+    subTitle?: string;
+    title: string;
+}
+export type {
+    IUserShortcutProps as default
+};

+ 56 - 0
src/core/contexts/coreComplex/cryptoPolyfill.ts

@@ -0,0 +1,56 @@
+import {
+    Buffer
+} from "buffer";
+import cryptoModule from "crypto";
+
+const _global = typeof window !== "undefined" ? window : (typeof global !== "undefined" ? global : null);
+if (_global) {
+    (_global as Record<string, unknown>).Buffer = (_global as Record<string, unknown>).Buffer || Buffer;
+    (_global as Record<string, unknown>).process = (_global as Record<string, unknown>).process || process;
+}
+
+if (typeof (cryptoModule as Record<string, unknown>).KeyObject === "undefined") {
+    class KeyObject {
+        type: string;
+        key: unknown;
+        constructor(type: string, key: unknown) {
+            this.type = type;
+            this.key = key;
+        }
+        export() {
+            return this.key;
+        }
+    }
+
+    Object.assign(cryptoModule, {
+        createSecretKey: function(key: unknown) {
+            const buf = (typeof key === "string" ? Buffer.from(key) : (Buffer.isBuffer(key) ? key : Buffer.from(String(key)))) as Buffer & { type: string, export: () => Buffer };
+            buf.type = "secret";
+            buf.export = () => buf;
+            return buf;
+        },
+        createPrivateKey: function(key: unknown) {
+            const keyObj = key as Record<string, unknown>;
+            const keyStr = typeof key === "string" ? key : (keyObj?.key ? String(keyObj.key) : (keyObj?.toString ? String(keyObj.toString()) : ""));
+            if (typeof keyStr === "string" && keyStr.includes("-----BEGIN ")) {
+                const buf = Buffer.from(keyStr) as Buffer & { type: string, export: () => Buffer };
+                buf.type = "private";
+                buf.export = () => buf;
+                return buf;
+            }
+            throw new Error("Invalid private key");
+        },
+        createPublicKey: function(key: unknown) {
+            const keyObj = key as Record<string, unknown>;
+            const keyStr = typeof key === "string" ? key : (keyObj?.key ? String(keyObj.key) : (keyObj?.toString ? String(keyObj.toString()) : ""));
+            if (typeof keyStr === "string" && keyStr.includes("-----BEGIN ")) {
+                const buf = Buffer.from(keyStr) as Buffer & { type: string, export: () => Buffer };
+                buf.type = "public";
+                buf.export = () => buf;
+                return buf;
+            }
+            throw new Error("Invalid public key");
+        },
+        KeyObject
+    });
+}

+ 76 - 0
src/core/contexts/coreComplex/index.tsx

@@ -0,0 +1,76 @@
+import {
+    useEffect,
+    Fragment
+} from "react";
+import type {
+    ICoreComplexProviderProps,
+    ICoreComplexContext
+} from "./type";
+import "./cryptoPolyfill";
+import {
+    CoreComplexClient
+} from "core-complex-client";
+import NCoreContext, {
+    type ConfigType
+} from "ncore-context";
+
+class NCoreUIKitCoreComplex extends NCoreContext<ICoreComplexContext, ConfigType<ICoreComplexContext>> {
+    constructor() {
+        super({
+            client: null
+        }, {
+            key: "NCoreUIKit-CoreComplexContext"
+        });
+    }
+
+    Render = ({
+        privateKeyID,
+        rsaPublicKey,
+        privateKey,
+        children,
+        appID
+    }: ICoreComplexProviderProps) => {
+        useEffect(() => {
+            if(!this.state.client) {
+                this.setState({
+                    client: new CoreComplexClient({
+                        privateKeyID,
+                        rsaPublicKey,
+                        privateKey,
+                        appID
+                    })
+                });
+            }
+        }, [
+            privateKeyID,
+            rsaPublicKey,
+            privateKey,
+            appID
+        ]);
+
+        return <Fragment>
+            {children}
+        </Fragment>;
+    };
+}
+
+const ncoreUIKitCoreComplex = new NCoreUIKitCoreComplex();
+
+export const useContext = ncoreUIKitCoreComplex.useContext;
+
+const CoreComplexProvider = (props: ICoreComplexProviderProps) => {
+    const Provider = ncoreUIKitCoreComplex.Provider;
+    const Render = ncoreUIKitCoreComplex.Render;
+
+    return <Provider>
+        <Render
+            privateKeyID={props.privateKeyID}
+            rsaPublicKey={props.rsaPublicKey}
+            privateKey={props.privateKey}
+            appID={props.appID}
+        >
+            {props.children}
+        </Render>
+    </Provider>;
+};
+export default CoreComplexProvider;

+ 23 - 0
src/core/contexts/coreComplex/type.ts

@@ -0,0 +1,23 @@
+import type {
+    ReactNode
+} from "react";
+import type {
+    CoreComplexClient
+} from "core-complex-client";
+
+interface ICoreComplexProviderProps {
+    rsaPublicKey: string;
+    privateKeyID: string;
+    children: ReactNode;
+    privateKey: string;
+    appID: string;
+}
+
+interface ICoreComplexContext {
+    client: CoreComplexClient | null;
+}
+
+export type {
+    ICoreComplexProviderProps,
+    ICoreComplexContext
+};

+ 9 - 1
src/index.tsx

@@ -15,6 +15,11 @@ export {
     NCoreUIKitMenu
 } from "./core/hooks";
 
+export {
+    default as NCoreUIKitCoreComplexProvider,
+    useContext as NCoreUIKitCoreComplex
+} from "./core/contexts/coreComplex";
+
 export {
     NotificationIndicator,
     HighlightButton,
@@ -29,6 +34,7 @@ export {
     TextAreaInput,
     ThemeSwitcher,
     SystemToolbar,
+    UserShortcut,
     WebScrollbar,
     DateSelector,
     EmbeddedMenu,
@@ -77,7 +83,6 @@ export type {
     INotificationIndicatorProps,
     AvatarGroupDynamicStyleType,
     AvatarGroupSizeConstantType,
-    AvatarStatusIndicatorType,
     CheckBoxDynamicStyleType,
     CheckBoxTypeConstantType,
     DateTimePickerPickerType,
@@ -108,6 +113,7 @@ export type {
     ITextAreaInputProps,
     IThemeSwitcherProps,
     ISystemToolbarProps,
+    IUserShortcutProps,
     AvatarMeasuresKeys,
     DateTimePickerType,
     EnterMarkdownTypes,
@@ -217,6 +223,7 @@ export type {
     PortalizedComponentProps,
     INCoreUIKitIconProps,
     GapPropagationType,
+    ActivityStatusType,
     NCoreUIKitConfig,
     RecursiveRecord,
     NCoreUIKitIcon,
@@ -241,6 +248,7 @@ export {
 
 export {
     androidTypographyFixer,
+    STATUS_CHEAT_SHEET,
     windowHeight,
     windowWidth,
     webStyle,

+ 4 - 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">;
@@ -275,6 +277,8 @@ declare global {
                 screen: string
             }) => void;
             getState: () => NavigationState;
+            canGoBack: () => boolean;
+            goBack: () => void;
         };
     }
 }

+ 27 - 0
src/utils/index.ts

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

+ 16 - 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,16 +39,21 @@
             "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",
+            "register": "Kayıt Ol",
+            "logout": "Çıkış Yap",
             "preview": "Önizleme",
+            "login": "Giriş Yap",
             "home": "Ana Sayfa",
             "monthly": "Aylık",
             "yearly": "Yıllık",
@@ -155,11 +163,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 +178,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",
@@ -175,12 +188,15 @@
             "is-optional": "Optional",
             "clean-all": "Clean All",
             "end-time": "End Time",
+            "register": "Register",
             "monthly": "Monthly",
             "preview": "Preview",
             "cancel": "Cancel",
+            "logout": "Logout",
             "search": "Search",
             "yearly": "Yearly",
             "daily": "Daily",
+            "login": "Login",
             "write": "Write",
             "home": "Home",
             "live": "Live",

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 617 - 9
yarn.lock


Kaikkia tiedostoja ei voida näyttää, sillä liian monta tiedostoa muuttui tässä diffissä