Forráskód Böngészése

Feature: Tooltip start.

lfabl 2 hete
szülő
commit
162095e9b6

+ 3 - 0
.agents/AGENTS.md

@@ -47,3 +47,6 @@ Always follow these rules by default. Pay "extreme attention" (aşırı dikkat)
 - Never leave inline comments in the code explaining what the style or line does.
 - Remove all inline comments unless they are strictly necessary (e.g., leaving something broken intentionally to fix later).
 - Keep the codebase clean and free of conversational or explanatory comments.
+
+## Communication Language
+- ALWAYS communicate in Turkish (Türkçe konuş) with the user in all responses, explanations, and notes, unless strictly required otherwise.

+ 0 - 1
.gitattributes

@@ -1,3 +1,2 @@
 *.pbxproj -text
-# specific for windows script files
 *.bat text eol=crlf

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

@@ -73,6 +73,7 @@ import SnackBarPage from "../pages/components/snackBar";
 import LoadingPage from "../pages/components/loading";
 import RowCardPage from "../pages/components/rowCard";
 import StickerPage from "../pages/components/sticker";
+import TooltipPage from "../pages/components/tooltip";
 import InstallationPage from "../pages/installation";
 import AvatarPage from "../pages/components/avatar";
 import ButtonPage from "../pages/components/button";
@@ -318,6 +319,10 @@ const ComponentsNav = () => {
             component={ToastPage}
             name="Toast"
         />
+        <ComponentsStack.Screen
+            component={TooltipPage}
+            name="Tooltip"
+        />
         <ComponentsStack.Screen
             component={WebScrollbarPage}
             name="WebScrollbar"
@@ -647,6 +652,11 @@ const RootNav = () => {
                             redirectSub: "Toast",
                             title: "Toast"
                         },
+                        {
+                            redirectMain: "Components",
+                            redirectSub: "Tooltip",
+                            title: "Tooltip"
+                        },
                         {
                             redirectMain: "Components",
                             redirectSub: "Loading",
@@ -904,6 +914,7 @@ const Navigation = () => {
                             ThemeSwitcher: "themeswitcher",
                             TimeSelector: "timeselector",
                             Toast: "toast",
+                            Tooltip: "tooltip",
                             WebScrollbar: "webscrollbar",
                             YearSelector: "yearselector"
                         }

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

@@ -49,6 +49,7 @@ export type ComponentsStackParamList = {
     Loading: undefined;
     RowCard: undefined;
     Sticker: undefined;
+    Tooltip: undefined;
     Avatar: undefined;
     Button: undefined;
     Dialog: undefined;

+ 243 - 0
example/src/pages/components/tooltip/index.tsx

@@ -0,0 +1,243 @@
+import {
+    useLayoutEffect,
+    useState
+} from "react";
+import {
+    View
+} from "react-native";
+import stylesheet from "./stylesheet";
+import {
+    NCoreUIKitLocalize,
+    NCoreUIKitTheme,
+    PageContainer,
+    CodeViewer,
+    SelectBox,
+    Tooltip,
+    Header,
+    Button,
+    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";
+import type {
+    TooltipLocation,
+    ITooltipProps
+} from "ncore-ui-kit";
+
+const TOOLTIP_TYPES: Array<ITooltipProps["type"]> = [
+    "primary",
+    "danger",
+    "warning",
+    "success",
+    "info",
+    "neutral"
+];
+
+const TOOLTIP_TYPES_DATA = TOOLTIP_TYPES.map(item => ({
+    __key: item as string,
+    __title: item as string
+}));
+
+const TOOLTIP_LOCATIONS: Array<TooltipLocation> = [
+    {
+        horizontal: "center",
+        vertical: "top"
+    },
+    {
+        horizontal: "center",
+        vertical: "bottom"
+    },
+    {
+        horizontal: "left",
+        vertical: "center"
+    },
+    {
+        horizontal: "right",
+        vertical: "center"
+    }
+];
+
+const TOOLTIP_LOCATIONS_DATA = [
+    {
+        __key: "0",
+        __title: "Top"
+    },
+    {
+        __key: "1",
+        __title: "Bottom"
+    },
+    {
+        __key: "2",
+        __title: "Left"
+    },
+    {
+        __key: "3",
+        __title: "Right"
+    }
+];
+
+const TooltipPage = () => {
+    const {
+        radiuses,
+        borders,
+        spaces,
+        colors
+    } = NCoreUIKitTheme.useContext();
+
+    const {
+        localize
+    } = NCoreUIKitLocalize.useContext();
+
+    const navigation = useNavigation<NativeStackNavigationProp<RootStackParamList>>();
+
+    const [
+        typeIndex,
+        setTypeIndex
+    ] = useState<number>(0);
+
+    const [
+        locationIndex,
+        setLocationIndex
+    ] = useState<number>(0);
+
+    useLayoutEffect(() => {
+        navigation.setOptions({
+            headerShown: true,
+            header: () => <Header
+                isWrapSafeareaContext={false}
+                title={localize("tooltip")}
+                navigation={navigation}
+                isGoBackEnable={true}
+            />
+        });
+    }, []);
+
+    return <PageContainer
+        scrollViewProps={{
+            contentContainerStyle: stylesheet.scrollContent
+        }}
+        scrollViewStyle={[
+            stylesheet.container
+        ]}
+        isWorkWithHeaderSpace={false}
+        isScrollable={true}
+    >
+        <View
+            style={[
+                stylesheet.contentContainer
+            ]}
+        >
+            <Text
+                style={stylesheet.descText}
+                variant="bodyMediumSize"
+                color="mid"
+            >
+                {localize("tooltip-desc")}
+            </Text>
+            <View
+                style={[
+                    stylesheet.previewContainer,
+                    {
+                        borderColor: colors.content.border.subtle,
+                        marginBottom: spaces.spacingMd,
+                        borderRadius: radiuses.form,
+                        borderWidth: borders.line,
+                        padding: spaces.spacingMd,
+                        paddingVertical: spaces.spacingLg
+                    }
+                ]}
+            >
+                <Tooltip
+                    subTitle={localize("example-tooltip-subtitle")}
+                    location={TOOLTIP_LOCATIONS[locationIndex]}
+                    title={localize("example-tooltip-title")}
+                    type={TOOLTIP_TYPES[typeIndex]}
+                >
+                    <Button
+                        title={localize("hover-or-press-me")}
+                        onPress={() => {}}
+                    />
+                </Tooltip>
+            </View>
+            <View
+                style={{
+                    marginBottom: spaces.spacingMd
+                }}
+            >
+                <Text
+                    style={{
+                        marginBottom: spaces.spacingSm
+                    }}
+                    variant="headlineSmallSize"
+                >
+                    {localize("usage")}
+                </Text>
+                <CodeViewer
+                    code={"import {\n    Tooltip,\n    Button\n} from \"ncore-ui-kit\";\n\n<Tooltip\n    location={{\n        horizontal: \"center\",\n        vertical: \"top\"\n    }}\n    title=\"Tooltip Title\"\n    subTitle=\"Tooltip Subtitle\"\n    type=\"primary\"\n>\n    <Button\n        title=\"Hover Me\"\n        onPress={() => {}}\n    />\n</Tooltip>"}
+                    language="tsx"
+                />
+            </View>
+            <View
+                style={[
+                    stylesheet.rowContainer,
+                    {
+                        gap: spaces.spacingMd
+                    }
+                ]}
+            >
+                <SelectBox
+                    onChange={(selectedItems) => {
+                        if (selectedItems.length > 0) {
+                            const index = TOOLTIP_TYPES.indexOf(selectedItems[0]!.__key as ITooltipProps["type"]);
+                            if (index !== -1) setTypeIndex(index);
+                        }
+                    }}
+                    initialSelectedItems={
+                        TOOLTIP_TYPES_DATA[typeIndex]
+                            ? [
+                                TOOLTIP_TYPES_DATA[typeIndex]!
+                            ]
+                            : []
+                    }
+                    titleExtractor={(item) => item.__title}
+                    keyExtractor={(item) => item.__key}
+                    title={localize("change-type")}
+                    data={TOOLTIP_TYPES_DATA}
+                    spreadBehaviour="free"
+                />
+                <SelectBox
+                    initialSelectedItems={
+                        TOOLTIP_LOCATIONS_DATA[locationIndex]
+                            ? [
+                                {
+                                    ...TOOLTIP_LOCATIONS_DATA[locationIndex]!,
+                                    __title: localize(TOOLTIP_LOCATIONS_DATA[locationIndex]!.__title.toLowerCase() as keyof NCoreUIKit.Translation)
+                                }
+                            ]
+                            : []
+                    }
+                    onChange={(selectedItems) => {
+                        if (selectedItems.length > 0) {
+                            const index = parseInt(selectedItems[0]!.__key);
+                            if (!isNaN(index)) setLocationIndex(index);
+                        }
+                    }}
+                    data={TOOLTIP_LOCATIONS_DATA.map(item => ({
+                        ...item,
+                        __title: localize(item.__title.toLowerCase() as keyof NCoreUIKit.Translation)
+                    }))}
+                    titleExtractor={(item) => item.__title}
+                    title={localize("change-location")}
+                    keyExtractor={(item) => item.__key}
+                    spreadBehaviour="free"
+                />
+            </View>
+        </View>
+    </PageContainer>;
+};
+export default TooltipPage;

+ 33 - 0
example/src/pages/components/tooltip/stylesheet.ts

@@ -0,0 +1,33 @@
+import {
+    StyleSheet
+} from "react-native";
+
+const stylesheet = StyleSheet.create({
+    rowContainer: {
+        justifyContent: "space-between",
+        alignItems: "center",
+        flexDirection: "row",
+        flexWrap: "wrap",
+        width: "100%"
+    },
+    previewContainer: {
+        justifyContent: "center",
+        alignItems: "center",
+        minHeight: 120
+    },
+    scrollContent: {
+        justifyContent: "center",
+        alignItems: "center"
+    },
+    contentContainer: {
+        width: "100%",
+        maxWidth: 850
+    },
+    descText: {
+        marginBottom: 20
+    },
+    container: {
+        flex: 1
+    }
+});
+export default stylesheet;

+ 20 - 2
example/src/variants/locales/default.json

@@ -13,6 +13,7 @@
             "defaultJSONs-themeDesc": "Uygulamanızın renk paletlerini, tipografisini ve boşluk değerlerini belirleyen tema konfigürasyonu örneği.",
             "installation-desc": "NCore UI Kit kurulumu, temel kullanımı ve geliştirme ortamınıza nasıl entegre edeceğiniz hakkında bilgiler.",
             "typesOverride-desc": "Kendi projelerinizde NCore UI Kit tiplerini (tema, dil vb.) global.d.ts ile nasıl ezebileceğinizin örneği.",
+            "tooltip-desc": "Bir bileşenin üzerine gelindiğinde veya basılı tutulduğunda ekstra bilgi sağlayan küçük bir bilgi kutucuğudur.",
             "webScrollbar-desc": "Web platformlarında tarayıcının varsayılan kaydırma çubuğunu özel temalı bir çubukla değiştiren bileşen.",
             "defaultJSONs-localeDesc": "Uygulamanızın desteklediği dilleri ve çevirileri barındıran yerelleştirme konfigürasyonu örneği.",
             "markdownViewer-desc": "Markdown formatındaki zengin metinleri ayrıştırıp biçimlendirilmiş olarak gösteren içerik bileşeni.",
@@ -166,6 +167,8 @@
             "components-localeSwitcher": "Dil Değiştirici",
             "components-pageContainer": "Sayfa Konteyneri",
             "components-themeSwitcher": "Tema Değiştirici",
+            "components-tooltip": "Bilgi Balonu (Tooltip)",
+            "example-tooltip-subtitle": "Örnek Alt Başlık",
             "highlightButton-toggleIcon": "İkonu Aç/Kapat",
             "rowCard-toggleLeftIcon": "Sol İkonu Aç/Kapat",
             "switch-changeIconDir": "İkon Yönünü Değiştir",
@@ -198,6 +201,7 @@
             "dialog-changeVariant": "Varyantı Değiştir",
             "dialog-openDialog": "İletişim Kutusunu Aç",
             "enter-image-url": "Resim Bağlantısı Girin",
+            "hover-or-press-me": "Üzerime Gel veya Bas",
             "installation-installationTitle": "Kurulum",
             "numericInput-toggleIcon": "İkonu Aç/Kapat",
             "rowCard-enterSubTitle": "Alt Başlık Girin",
@@ -245,6 +249,7 @@
             "components-monthSelector": "Ay Seçici",
             "components-yearSelector": "Yıl Seçici",
             "date-time-picker": "Tarih Saat Seçici",
+            "example-tooltip-title": "Örnek Başlık",
             "loading-changeSize": "Boyutu Değiştir",
             "loading-title": "Yükleniyor (Loading)",
             "localeExamples-title": "Dil Örnekleri",
@@ -287,6 +292,7 @@
             "textAreaInput-title": "Metin Alanı",
             "textAreaInput-titleLabel": "Başlık",
             "avatarGroup-title": "Avatar Grubu",
+            "change-location": "Konum Değiştir",
             "checkBox-enterText": "Metin Girin",
             "enter-sub-text": "Alt Metin Girin",
             "numericInput-titleLabel": "Başlık",
@@ -370,13 +376,16 @@
             "usage": "Kullanım",
             "avatar": "Avatar",
             "button": "Button",
+            "tooltip": "İpucu",
             "version": "Sürüm",
             "next": "Sonraki",
             "title": "Başlık",
             "close": "Kapat",
+            "bottom": "Alt",
             "right": "Sağ",
             "chip": "Çip",
-            "left": "Sol"
+            "left": "Sol",
+            "top": "Üst"
         },
         "locale": "tr-TR",
         "isRTL": false
@@ -412,6 +421,7 @@
             "localeSwitcher-desc": "A control component that allows instant switching between the application's language options.",
             "dateTimeSheet-desc": "A sheet component that opens from the bottom of the screen to allow date and time selection.",
             "radioButton-desc": "A button component used to select exactly one option from a set of mutually exclusive choices.",
+            "tooltip-desc": "A small informational box that provides extra details when hovering or long-pressing a component.",
             "typesOverride-tsconfigDesc": "Don't forget to add your global.d.ts to typeRoots or include in your tsconfig.json.",
             "numericInput-desc": "A specialized input component that only allows numerical data or quantities to be entered.",
             "modal-desc": "A modal window component that overlays the current page and focuses the user on a specific task.",
@@ -502,6 +512,7 @@
             "bottom-sheet-content": "Bottom Sheet Content",
             "components-localeSwitcher": "Locale Switcher",
             "dateTimePicker-changeSpread": "Change Spread",
+            "example-tooltip-subtitle": "Example Subtitle",
             "ncore-design-system": "NCore | Design System",
             "numericInput-changeVariant": "Change Variant",
             "rowCard-toggleRightIcon": "Toggle Right Icon",
@@ -587,8 +598,10 @@
             "components-selectSheet": "Select Sheet",
             "dialog-changeVariant": "Change Variant",
             "embeddedMenu-home": "EmbeddedMenu Home",
+            "example-tooltip-title": "Example Title",
             "header-enterTitle": "Header EnterTitle",
             "header-titleLabel": "Header TitleLabel",
+            "hover-or-press-me": "Hover or Press Me",
             "implementation-title": "Implementation",
             "markdownViewer-contentLabel": "Content",
             "numericInput-changeType": "Change Type",
@@ -631,6 +644,7 @@
             "textInput-toggleIcon": "Toggle Icon",
             "webScrollbar-title": "Web Scrollbar",
             "change-language": "Change Language",
+            "change-location": "Change Location",
             "checkBox-changeType": "Change Type",
             "checkBox-toggleFlip": "Toggle Flip",
             "chip-changeSpread": "Change Spread",
@@ -689,6 +703,7 @@
             "menu-settings": "Menu Settings",
             "themeExamples-danger": "Danger",
             "toggle-portal": "Toggle Portal",
+            "components-tooltip": "Tooltip",
             "coreFeatures": "Core Features",
             "defaultJSONs": "Default JSONs",
             "selectBox-title": "Select Box",
@@ -746,9 +761,11 @@
             "switch-title": "Switch",
             "chip-title": "Chip",
             "content": "Content",
+            "tooltip": "Tooltip",
             "version": "Version",
             "home": "Home Page",
             "avatar": "Avatar",
+            "bottom": "Bottom",
             "button": "Button",
             "dialog": "Dialog",
             "close": "Close",
@@ -758,7 +775,8 @@
             "chip": "Chip",
             "left": "Left",
             "next": "Next",
-            "text": "Text"
+            "text": "Text",
+            "top": "Top"
         },
         "locale": "en-US",
         "isRTL": false

+ 15 - 6
src/components/index.ts

@@ -31,8 +31,8 @@ export {
 } from "./dialog";
 
 export type {
-    default as IDialogProps,
     DialogDynamicStyleType,
+    default as IDialogProps,
     DialogContentJustify,
     DialogVariant,
     IDialogRef
@@ -95,9 +95,9 @@ export {
 
 export type {
     CheckBoxDisplayBehaviourWhileLoading,
-    default as ICheckBoxProps,
     CheckBoxDynamicStyleType,
     CheckBoxTypeConstantType,
+    default as ICheckBoxProps,
     CheckBoxSpreadBehaviour,
     CheckBoxTypes,
     CheckBoxType
@@ -173,9 +173,9 @@ export type {
 } from "./dateTimeSheet/type";
 
 export type {
-    default as IDateTimePickerProps,
     DateTimePickerDateRangeResponse,
     DateTimePickerDynamicStyleType,
+    default as IDateTimePickerProps,
     DateTimePickerSpreadBehaviour,
     DateTimePickerPickerType,
     DateTimePickerDateRange,
@@ -256,9 +256,9 @@ export {
 
 export type {
     ChipDisplayBehaviourWhileLoading,
-    default as IChipProps,
     ChipDynamicStyleType,
     ChipTypeConstantType,
+    default as IChipProps,
     ChipSpreadBehaviour,
     ChipSizeType,
     ChipTypes,
@@ -296,9 +296,9 @@ export {
 export type {
     AvatarTitleAbbreviationTypes,
     AvatarStatusIndicatorType,
-    default as IAvatarProps,
     AvatarDynamicStyleType,
     AvatarSizeConstantType,
+    default as IAvatarProps,
     AvatarMeasuresKeys,
     AvatarMeasures,
     AvatarSizeType
@@ -310,9 +310,9 @@ export {
 
 export type {
     AvatarGroupTitleAbbreviationTypes,
-    default as IAvatarGroupProps,
     AvatarGroupDynamicStyleType,
     AvatarGroupSizeConstantType,
+    default as IAvatarGroupProps,
     AvatarGroupMeasuresKeys,
     AvatarGroupAvatarsType,
     AvatarGroupSizeType
@@ -438,3 +438,12 @@ export {
 export type {
     default as ICodeEditorProps
 } from "./codeEditor/type";
+
+export {
+    default as Tooltip
+} from "./tooltip";
+
+export type {
+    default as ITooltipProps,
+    TooltipLocation
+} from "./tooltip/type";

+ 400 - 0
src/components/tooltip/index.tsx

@@ -0,0 +1,400 @@
+import {
+    useEffect,
+    useState,
+    type FC,
+    useRef
+} from "react";
+import {
+    TouchableWithoutFeedback,
+    type ViewStyle,
+    Animated,
+    Easing,
+    View
+} from "react-native";
+import type ITooltipProps from "./type";
+import stylesheet, {
+    getTooltipType,
+    useStyles
+} from "./stylesheet";
+import {
+    NCoreUIKitTheme
+} from "../../core/hooks";
+import {
+    X as XIcon
+} from "lucide-react-native";
+import Button from "../button";
+import Text from "../text";
+
+const Tooltip: FC<ITooltipProps> = ({
+    isCloseOnPressActionButton = true,
+    location = {
+        horizontal: "center",
+        vertical: "top"
+    },
+    isCloseOnPress = false,
+    icon: CustomIcon,
+    type = "neutral",
+    customTheme,
+    isVisible,
+    onClosed,
+    subTitle,
+    children,
+    onClose,
+    onOpen,
+    content,
+    action,
+    style,
+    title
+}) => {
+    const {
+        radiuses,
+        colors,
+        spaces
+    } = NCoreUIKitTheme.useContext(customTheme);
+
+    const [
+        wrapperSize,
+        setWrapperSize
+    ] = useState({
+        height: 0,
+        width: 0
+    });
+
+    const [
+        tooltipSize,
+        setTooltipSize
+    ] = useState({
+        height: 0,
+        width: 0
+    });
+
+    const [
+        isInternalVisible,
+        setIsInternalVisible
+    ] = useState(false);
+
+    const opacityAnim = useRef(new Animated.Value(0)).current;
+
+    const currentType = getTooltipType({
+        type
+    });
+
+    const {
+        containerObject: containerObjectDynamicStyle,
+        contentContainer: contentContainerDynamicStyle,
+        iconContainer: iconContainerDynamicStyle,
+        container: containerDynamicStyle,
+        subTitle: subTitleDynamicStyle,
+        action: actionDynamicStyle,
+        title: titleDynamicStyle,
+        caret: caretDynamicStyle
+    } = useStyles({
+        currentType,
+        radiuses,
+        colors,
+        spaces,
+        type
+    });
+
+    const isShow = isVisible !== undefined ? isVisible : isInternalVisible;
+
+    useEffect(() => {
+        if(isShow) {
+            if(onOpen) onOpen();
+
+            Animated.timing(opacityAnim, {
+                useNativeDriver: true,
+                easing: Easing.linear,
+                duration: 200,
+                toValue: 1
+            }).start();
+        } else {
+            Animated.timing(opacityAnim, {
+                useNativeDriver: true,
+                easing: Easing.linear,
+                duration: 200,
+                toValue: 0
+            }).start(({
+                finished
+            }) => {
+                if(finished && onClosed) {
+                    onClosed();
+                }
+            });
+        }
+    }, [
+        isShow
+    ]);
+
+    const handleClose = () => {
+        if(isVisible === undefined) {
+            setIsInternalVisible(false);
+        }
+        if(onClose) onClose();
+    };
+
+    const handleToggle = () => {
+        if(isVisible === undefined) {
+            setIsInternalVisible(!isInternalVisible);
+        }
+        if(!isShow && onOpen) {
+            onOpen();
+        } else if (isShow && onClose) {
+            onClose();
+        }
+    };
+
+    const renderIcon = () => {
+        if(!CustomIcon) {
+            return null;
+        }
+
+        return <View
+            style={[
+                stylesheet.iconContainer,
+                iconContainerDynamicStyle
+            ]}
+        >
+            <CustomIcon
+                color={currentType.iconColor}
+                size={16}
+            />
+        </View>;
+    };
+
+    const renderContent = () => {
+        if(content) {
+            if(typeof content === "function") {
+                return content();
+            }
+
+            return content;
+        }
+
+        return <View
+            style={[
+                stylesheet.contentContainer,
+                contentContainerDynamicStyle
+            ]}
+        >
+            {
+                title ?
+                    <Text
+                        style={{
+                            ...stylesheet.title,
+                            ...titleDynamicStyle
+                        }}
+                        color={currentType.titleColor}
+                        variant="labelMediumSize"
+                        numberOfLines={3}
+                    >
+                        {title}
+                    </Text>
+                :
+                    null
+            }
+            {
+                subTitle ?
+                    <Text
+                        style={{
+                            ...stylesheet.subTitle,
+                            ...subTitleDynamicStyle
+                        }}
+                        color={currentType.subTitleColor}
+                        variant="labelSmallSize"
+                        numberOfLines={2}
+                    >
+                        {subTitle}
+                    </Text>
+                :
+                    null
+            }
+        </View>;
+    };
+
+    const renderAction = () => {
+        if(!action) {
+            return null;
+        }
+
+        return <Button
+            onPress={() => {
+                if(isCloseOnPressActionButton) handleClose();
+
+                if(action.onPress) {
+                    action.onPress({
+                        closeAnimation: handleClose
+                    });
+                }
+            }}
+            icon={() => {
+                if(action.title) {
+                    return null;
+                }
+
+                return <XIcon
+                    color={colors.content.text[currentType.actionColor]}
+                    size={16}
+                />;
+            }}
+            style={{
+                ...action.style,
+                ...actionDynamicStyle
+            }}
+            title={action.title ? action.title : undefined}
+            spreadBehaviour="free"
+            isCustomPadding={true}
+            variant="ghost"
+            size="small"
+            type={type}
+        />;
+    };
+
+    const getTooltipStyle = (): ViewStyle => {
+        const _style: ViewStyle = {};
+
+        if(tooltipSize.width > 0 && wrapperSize.width > 0) {
+            if(location.vertical === "top") {
+                _style.bottom = wrapperSize.height + 8;
+            } else if (location.vertical === "bottom") {
+                _style.top = wrapperSize.height + 8;
+            } else {
+                _style.top = (wrapperSize.height - tooltipSize.height) / 2;
+            }
+
+            if(location.horizontal === "left") {
+                _style.right = wrapperSize.width + 8;
+            } else if (location.horizontal === "right") {
+                _style.left = wrapperSize.width + 8;
+            } else {
+                _style.left = (wrapperSize.width - tooltipSize.width) / 2;
+            }
+        } else {
+            _style.opacity = 0;
+        }
+
+        return _style;
+    };
+
+    const getCaretStyle = (): ViewStyle => {
+        const _style: ViewStyle = {};
+
+        if(location.vertical === "top") {
+            _style.bottom = -4;
+        } else if (location.vertical === "bottom") {
+            _style.top = -4;
+        } else {
+            _style.top = (tooltipSize.height - 12) / 2;
+        }
+
+        if(location.horizontal === "left") {
+            _style.right = -4;
+        } else if (location.horizontal === "right") {
+            _style.left = -4;
+        } else {
+            _style.left = (tooltipSize.width - 12) / 2;
+        }
+
+        if (location.vertical === "top" && location.horizontal === "right") {
+            _style.left = 12;
+        } else if (location.vertical === "top" && location.horizontal === "left") {
+            _style.right = 12;
+        } else if (location.vertical === "bottom" && location.horizontal === "right") {
+            _style.left = 12;
+        } else if (location.vertical === "bottom" && location.horizontal === "left") {
+            _style.right = 12;
+        }
+
+        return _style;
+    };
+
+    return <View
+        onLayout={(e) => {
+            const {
+                height,
+                width
+            } = e.nativeEvent.layout;
+
+            setWrapperSize(prev => {
+                if (prev.height === height && prev.width === width) {
+                    return prev;
+                }
+                return {
+                    height,
+                    width
+                };
+            });
+        }}
+        style={[
+            stylesheet.wrapper,
+            style
+        ]}
+    >
+        <TouchableWithoutFeedback
+            onPress={() => {
+                if(!isCloseOnPress) {
+                    handleToggle();
+                } else if(isShow) {
+                    handleClose();
+                } else {
+                    handleToggle();
+                }
+            }}
+        >
+            <View>
+                {children}
+            </View>
+        </TouchableWithoutFeedback>
+        {
+            isShow || opacityAnim !== undefined ?
+                <Animated.View
+                    onLayout={(e) => {
+                        const {
+                            height,
+                            width
+                        } = e.nativeEvent.layout;
+
+                        setTooltipSize(prev => {
+                            if (prev.height === height && prev.width === width) {
+                                return prev;
+                            }
+                            return {
+                                height,
+                                width
+                            };
+                        });
+                    }}
+                    style={[
+                        stylesheet.container,
+                        containerDynamicStyle,
+                        getTooltipStyle(),
+                        {
+                            opacity: opacityAnim
+                        }
+                    ]}
+                    pointerEvents={isShow ? "auto" : "none"}
+                >
+                    <View
+                        style={[
+                            stylesheet.caret,
+                            caretDynamicStyle,
+                            getCaretStyle()
+                        ]}
+                    />
+                    <View
+                        style={[
+                            stylesheet.containerObject,
+                            containerObjectDynamicStyle
+                        ]}
+                    >
+                        {renderIcon()}
+                        {renderContent()}
+                        {renderAction()}
+                    </View>
+                </Animated.View>
+            :
+                null
+        }
+    </View>;
+};
+export default Tooltip;

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

@@ -0,0 +1,167 @@
+import {
+    type TextStyle,
+    type ViewStyle,
+    StyleSheet
+} from "react-native";
+import type {
+    TooltipTypeConstantType,
+    TooltipDynamicStyleType,
+    TooltipTypes,
+    TooltipType
+} from "./type";
+import type {
+    Mutable
+} from "../../types";
+import {
+    windowWidth
+} from "../../utils";
+
+export const TOOLTIP_TYPE_STYLES: Record<
+    TooltipType,
+    {
+        containerColor: keyof NCoreUIKit.ContainerContentColors;
+        subTitleColor: keyof NCoreUIKit.TextContentColors;
+        actionColor: keyof NCoreUIKit.TextContentColors;
+        titleColor: keyof NCoreUIKit.TextContentColors;
+        iconColor: keyof NCoreUIKit.IconContentColors;
+    }
+> = {
+    primary: {
+        actionColor: "onPrimary",
+        iconColor: "emphasized",
+        containerColor: "primary",
+        subTitleColor: "onPrimary",
+        titleColor: "onPrimary"
+    },
+    danger: {
+        subTitleColor: "dangerLow",
+        containerColor: "danger",
+        actionColor: "danger",
+        titleColor: "danger",
+        iconColor: "danger"
+    },
+    success: {
+        subTitleColor: "successLow",
+        containerColor: "success",
+        actionColor: "success",
+        titleColor: "success",
+        iconColor: "success"
+    },
+    warning: {
+        subTitleColor: "warningLow",
+        containerColor: "warning",
+        actionColor: "warning",
+        titleColor: "warning",
+        iconColor: "warning"
+    },
+    info: {
+        subTitleColor: "infoLow",
+        containerColor: "info",
+        actionColor: "info",
+        titleColor: "info",
+        iconColor: "info"
+    },
+    neutral: {
+        containerColor: "mid",
+        subTitleColor: "low",
+        actionColor: "mid",
+        titleColor: "mid",
+        iconColor: "mid"
+    }
+};
+
+export const getTooltipType = ({
+    type
+}: TooltipTypeConstantType): TooltipTypes => {
+    const currentType = TOOLTIP_TYPE_STYLES[type];
+
+    return currentType;
+};
+
+const stylesheet = StyleSheet.create({
+    caret: {
+        transform: [
+            {
+                rotate: "45deg"
+            }
+        ],
+        position: "absolute",
+        height: 12,
+        width: 12
+    },
+    contentContainer: {
+        justifyContent: "center",
+        alignItems: "flex-start",
+        flexDirection: "column",
+        flexShrink: 1
+    },
+    wrapper: {
+        justifyContent: "center",
+        alignSelf: "flex-start",
+        position: "relative",
+        alignItems: "center"
+    },
+    container: {
+        position: "absolute",
+        zIndex: 99998,
+        minWidth: 50
+    },
+    iconContainer: {
+        justifyContent: "center",
+        alignItems: "center"
+    },
+    containerObject: {
+        flexDirection: "row",
+        alignItems: "center"
+    },
+    subTitle: {
+        textAlign: "left"
+    },
+    title: {
+        textAlign: "left"
+    }
+});
+
+export const useStyles = ({
+    currentType,
+    radiuses,
+    colors,
+    spaces
+}: TooltipDynamicStyleType) => {
+    const styles = {
+        action: {
+            paddingBottom: spaces.spacingSm,
+            paddingRight: spaces.spacingSm,
+            paddingLeft: spaces.spacingMd,
+            paddingTop: spaces.spacingSm,
+            marginLeft: spaces.spacingMd
+        } as Mutable<ViewStyle>,
+        container: {
+            backgroundColor: colors.content.container[currentType.containerColor],
+            maxWidth: windowWidth - (spaces.spacingMd * 2),
+            borderRadius: radiuses.md
+        } as Mutable<ViewStyle>,
+        containerObject: {
+            paddingHorizontal: spaces.spacingMd,
+            paddingVertical: spaces.spacingSm
+        } as Mutable<ViewStyle>,
+        iconContainer: {
+            paddingRight: spaces.spacingSm,
+            marginRight: spaces.spacingSm
+        } as Mutable<ViewStyle>,
+        caret: {
+            backgroundColor: colors.content.container[currentType.containerColor]
+        } as Mutable<ViewStyle>,
+        subTitle: {
+            marginTop: spaces.spacingXs
+        } as Mutable<TextStyle>,
+        contentContainer: {
+
+        } as Mutable<ViewStyle>,
+        title: {
+        } as Mutable<TextStyle>
+    };
+
+    return styles;
+};
+export default stylesheet;

+ 76 - 0
src/components/tooltip/type.ts

@@ -0,0 +1,76 @@
+import {
+    type ReactNode
+} from "react";
+import {
+    type StyleProp,
+    type ViewStyle
+} from "react-native";
+import type {
+    NCoreUIKitIcon
+} from "../../types";
+
+export type TooltipDynamicStyleType = {
+    radiuses: NCoreUIKit.ActivePalette["radiuses"];
+    spaces: NCoreUIKit.ActivePalette["spaces"];
+    colors: NCoreUIKit.ActivePalette["colors"];
+    currentType: TooltipTypes;
+    type: TooltipType;
+};
+
+export type TooltipTypes = {
+    containerColor: keyof NCoreUIKit.ContainerContentColors;
+    subTitleColor: keyof NCoreUIKit.TextContentColors;
+    actionColor: keyof NCoreUIKit.TextContentColors;
+    titleColor: keyof NCoreUIKit.TextContentColors;
+    iconColor: keyof NCoreUIKit.IconContentColors;
+};
+
+export type TooltipTypeConstantType = {
+    type: TooltipType;
+};
+
+export type TooltipType = "primary" | "danger" | "warning" | "neutral" | "success" | "info";
+
+export type TooltipLocation = {
+    horizontal: "left" | "center" | "right";
+    vertical: "top" | "center" | "bottom";
+};
+
+export type TooltipInternalProps = {
+    contentContainerStyle?: StyleProp<ViewStyle>[] | StyleProp<ViewStyle>;
+    style?: StyleProp<ViewStyle>[] | StyleProp<ViewStyle>;
+    customTheme?: {
+        gapPropagation?: keyof NCoreUIKit.GapPropagationKey;
+        sharpness?: keyof NCoreUIKit.SharpnessKey;
+        paletteKey?: keyof NCoreUIKit.PaletteKey;
+        themeKey?: keyof NCoreUIKit.ThemeKey;
+    };
+    content?: ReactNode | (() => ReactNode);
+    isCloseOnPressActionButton?: boolean;
+    action?: {
+        onPress: (props: {
+            closeAnimation: () => void;
+        }) => void;
+        icon?: NCoreUIKitIcon;
+        style?: ViewStyle;
+        title?: string;
+    };
+    location?: TooltipLocation;
+    isCloseOnPress?: boolean;
+    icon?: NCoreUIKitIcon;
+    onClosed?: () => void;
+    children?: ReactNode;
+    onClose?: () => void;
+    isVisible?: boolean;
+    onOpen?: () => void;
+    type?: TooltipType;
+    subTitle?: string;
+    title?: string;
+};
+
+interface ITooltipProps extends TooltipInternalProps {
+};
+
+export type {
+    ITooltipProps as default
+};

+ 3 - 0
src/index.tsx

@@ -52,6 +52,7 @@ export {
     Loading,
     RowCard,
     Sticker,
+    Tooltip,
     Button,
     Avatar,
     Dialog,
@@ -129,6 +130,7 @@ export type {
     IMainHeaderProps,
     IMenuButtonProps,
     ValidTitleFormat,
+    TooltipLocation,
     BlockquoteTypes,
     IBottomSheetRef,
     ISelectBoxProps,
@@ -141,6 +143,7 @@ export type {
     ISiteLogoProps,
     ISnackBarProps,
     MarkdownObject,
+    ITooltipProps,
     CheckBoxTypes,
     DialogVariant,
     ILoadingProps,