Selaa lähdekoodia

Feature: Markdown Editor added.

lfabl 2 viikkoa sitten
vanhempi
commit
bc21318a08

+ 48 - 37
example/src/navigation/index.tsx

@@ -33,56 +33,57 @@ import {
     GoalIcon,
     HomeIcon
 } from "lucide-react-native";
-import AvatarPage from "../pages/components/avatar";
-import AvatarGroupPage from "../pages/components/avatarGroup";
-import BottomSheetPage from "../pages/components/bottomSheet";
-import ButtonPage from "../pages/components/button";
-import CheckBoxPage from "../pages/components/checkBox";
-import ChipPage from "../pages/components/chip";
-import CodeViewerPage from "../pages/components/codeViewer";
-import DateSelectorPage from "../pages/components/dateSelector";
-import DateTimePickerPage from "../pages/components/dateTimePicker";
-import DateTimeSheetPage from "../pages/components/dateTimeSheet";
-import DialogPage from "../pages/components/dialog";
-import EmbeddedMenuPage from "../pages/components/embeddedMenu";
-import HeaderPage from "../pages/components/header";
+import NotificationIndicatorPage from "../pages/components/notificationIndicator";
 import HighlightButtonPage from "../pages/components/highlightButton";
-import LoadingPage from "../pages/components/loading";
+import PaletteSwitcherPage from "../pages/components/paletteSwitcher";
+import LocaleExamplesPage from "../pages/coreFeatures/localeExamples";
+import DateTimePickerPage from "../pages/components/dateTimePicker";
 import LocaleSwitcherPage from "../pages/components/localeSwitcher";
-import MainHeaderPage from "../pages/components/mainHeader";
 import MarkdownViewerPage from "../pages/components/markdownViewer";
-import MenuPage from "../pages/components/menu";
-import ModalPage from "../pages/components/modal";
+import MarkdownEditorPage from "../pages/components/markdownEditor";
+import ThemeExamplesPage from "../pages/coreFeatures/themeExamples";
 import MonthSelectorPage from "../pages/components/monthSelector";
-import NotificationIndicatorPage from "../pages/components/notificationIndicator";
-import NumericInputPage from "../pages/components/numericInput";
+import DateTimeSheetPage from "../pages/components/dateTimeSheet";
 import PageContainerPage from "../pages/components/pageContainer";
-import PaletteSwitcherPage from "../pages/components/paletteSwitcher";
+import TextAreaInputPage from "../pages/components/textAreaInput";
+import ThemeSwitcherPage from "../pages/components/themeSwitcher";
+import DefaultJSONsPage from "../pages/coreFeatures/defaultJSONs";
+import TimeSelectorPage from "../pages/components/timeSelector";
+import DateSelectorPage from "../pages/components/dateSelector";
+import NumericInputPage from "../pages/components/numericInput";
+import EmbeddedMenuPage from "../pages/components/embeddedMenu";
+import WebScrollbarPage from "../pages/components/webScrollbar";
+import YearSelectorPage from "../pages/components/yearSelector";
+import BottomSheetPage from "../pages/components/bottomSheet";
+import AvatarGroupPage from "../pages/components/avatarGroup";
+import ToolsUsagePage from "../pages/coreFeatures/toolsUsage";
 import RadioButtonPage from "../pages/components/radioButton";
-import RowCardPage from "../pages/components/rowCard";
-import SelectBoxPage from "../pages/components/selectBox";
 import SelectSheetPage from "../pages/components/selectSheet";
+import CodeViewerPage from "../pages/components/codeViewer";
+import MainHeaderPage from "../pages/components/mainHeader";
+import SelectBoxPage from "../pages/components/selectBox";
 import SeperatorPage from "../pages/components/seperator";
+import StateCardPage from "../pages/components/stateCard";
+import TextInputPage from "../pages/components/textInput";
+import ImplementationPage from "../pages/implementation";
+import CheckBoxPage from "../pages/components/checkBox";
 import SiteLogoPage from "../pages/components/siteLogo";
 import SnackBarPage from "../pages/components/snackBar";
-import StateCardPage from "../pages/components/stateCard";
+import LoadingPage from "../pages/components/loading";
+import RowCardPage from "../pages/components/rowCard";
 import StickerPage from "../pages/components/sticker";
+import InstallationPage from "../pages/installation";
+import AvatarPage from "../pages/components/avatar";
+import DialogPage from "../pages/components/dialog";
+import ButtonPage from "../pages/components/button";
+import HeaderPage from "../pages/components/header";
 import SwitchPage from "../pages/components/switch";
-import TextPage from "../pages/components/text";
-import TextAreaInputPage from "../pages/components/textAreaInput";
-import TextInputPage from "../pages/components/textInput";
-import ThemeSwitcherPage from "../pages/components/themeSwitcher";
-import TimeSelectorPage from "../pages/components/timeSelector";
+import ModalPage from "../pages/components/modal";
 import ToastPage from "../pages/components/toast";
-import WebScrollbarPage from "../pages/components/webScrollbar";
-import YearSelectorPage from "../pages/components/yearSelector";
-import DefaultJSONsPage from "../pages/coreFeatures/defaultJSONs";
-import LocaleExamplesPage from "../pages/coreFeatures/localeExamples";
-import ThemeExamplesPage from "../pages/coreFeatures/themeExamples";
-import ToolsUsagePage from "../pages/coreFeatures/toolsUsage";
+import ChipPage from "../pages/components/chip";
+import MenuPage from "../pages/components/menu";
+import TextPage from "../pages/components/text";
 import Home from "../pages/home";
-import ImplementationPage from "../pages/implementation";
-import InstallationPage from "../pages/installation";
 
 const RootStack = createNativeStackNavigator();
 const ComponentsStack = createNativeStackNavigator();
@@ -216,6 +217,10 @@ const ComponentsNav = () => {
             component={MarkdownViewerPage}
             name="MarkdownViewer"
         />
+        <ComponentsStack.Screen
+            component={MarkdownEditorPage}
+            name="MarkdownEditor"
+        />
         <ComponentsStack.Screen
             component={MenuPage}
             name="Menu"
@@ -647,6 +652,11 @@ const RootNav = () => {
                             redirectSub: "MarkdownViewer",
                             title: "MarkdownViewer"
                         },
+                        {
+                            redirectMain: "Components",
+                            redirectSub: "MarkdownEditor",
+                            title: "MarkdownEditor"
+                        },
                         {
                             redirectMain: "Components",
                             redirectSub: "Seperator",
@@ -814,7 +824,7 @@ const RootNav = () => {
 const Navigation = () => {
     return <NavigationContainer
         documentTitle={{
-            formatter: (options, route) => `NCore - Design System | UI Kit - ${options?.title ?? route?.name ?? "Home"}`
+            formatter: (options, route) => `${options?.title ?? route?.name ?? "Home"} || NCore - Design System | UI Kit`
         }}
         linking={{
             prefixes: [
@@ -862,6 +872,7 @@ const Navigation = () => {
                             LocaleSwitcher: "localeswitcher",
                             MainHeader: "mainheader",
                             MarkdownViewer: "markdownviewer",
+                            MarkdownEditor: "markdowneditor",
                             Menu: "menu",
                             Modal: "modal",
                             MonthSelector: "monthselector",

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

@@ -21,6 +21,7 @@ export type ComponentsStackParamList = {
     PaletteSwitcher: undefined;
     DateTimePicker: undefined;
     MarkdownViewer: undefined;
+    MarkdownEditor: undefined;
     LocaleSwitcher: undefined;
     DateTimeSheet: undefined;
     TextAreaInput: undefined;

+ 68 - 0
example/src/pages/components/markdownEditor/index.tsx

@@ -0,0 +1,68 @@
+import {
+    useLayoutEffect
+} from "react";
+import {
+    View
+} from "react-native";
+import stylesheet from "./stylesheet";
+import {
+    NCoreUIKitLocalize,
+    MarkdownEditor,
+    PageContainer,
+    Header,
+    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 MarkdownEditorPage = () => {
+    const {
+        localize
+    } = NCoreUIKitLocalize.useContext();
+
+    const navigation = useNavigation<NativeStackNavigationProp<RootStackParamList>>();
+
+    useLayoutEffect(() => {
+        navigation.setOptions({
+            header: () => <Header
+                title={localize("markdownEditor-title")}
+                isWrapSafeareaContext={false}
+                navigation={navigation}
+                isGoBackEnable={true}
+            />,
+            headerShown: true
+        });
+    }, []);
+
+    return <PageContainer
+        scrollViewProps={{
+            contentContainerStyle: stylesheet.scrollContent
+        }}
+        scrollViewStyle={[stylesheet.container]}
+        isWorkWithHeaderSpace={false}
+        isScrollable={true}
+    >
+        <View
+            style={[stylesheet.contentContainer]}
+        >
+            <Text
+                variant="bodyMediumSize"
+                color="mid"
+                style={stylesheet.marginContainer}
+            >
+                {localize("markdownEditor-desc")}
+            </Text>
+            <MarkdownEditor
+                initialValue="# Markdown Editor\n\nThis is a **MarkdownEditor** component from *NCore UI Kit*.\n\n- Write your markdown here."
+                style={stylesheet.editor}
+            />
+        </View>
+    </PageContainer>;
+};
+
+export default MarkdownEditorPage;

+ 27 - 0
example/src/pages/components/markdownEditor/stylesheet.ts

@@ -0,0 +1,27 @@
+import {
+    StyleSheet
+} from "react-native";
+
+const stylesheet = StyleSheet.create({
+    scrollContent: {
+        justifyContent: "center",
+        alignItems: "center"
+    },
+    contentContainer: {
+        maxWidth: 850,
+        width: "100%"
+    },
+    paddedMarginContainer: {
+        minHeight: 120
+    },
+    marginContainer: {
+        marginBottom: 20
+    },
+    editor: {
+        minHeight: 350
+    },
+    container: {
+        flex: 1
+    }
+});
+export default stylesheet;

+ 5 - 1
example/src/variants/locales/default.json

@@ -33,6 +33,7 @@
             "chip-desc": "Küçük bilgi parçalarını, etiketleri veya durumları göstermek için kullanılan kompakt bileşen.",
             "snackBar-desc": "Ekranın alt kısmında geçici olarak beliren ve kısa durum mesajları sunan uyarı bileşeni.",
             "toolsUsage-codeViewerDesc": "Kodu uygulamanızda güzel bir şekilde render etmek ve panoya kopyalamak için.",
+            "markdownEditor-desc": "Markdown formatındaki zengin metinleri düzenlemek için kullanılan içerik bileşeni.",
             "checkBox-desc": "Birden fazla seçenek arasından bağımsız seçimler yapmayı sağlayan onay kutusu bileşeni.",
             "embeddedMenu-desc": "Sayfa içine gömülü (inline) olarak çalışan ve bağlamsal menü öğeleri sunan bileşen.",
             "numericInput-desc": "Sadece sayısal verilerin veya miktarların girilmesini sağlayan özel girdi bileşeni.",
@@ -60,6 +61,7 @@
             "avatarGroup-desc": "Birden fazla avatarı bir arada gruplayarak gösteren bileşen.",
             "avatar-desc": "Kullanıcı veya varlıkları temsil eden profil görseli bileşeni.",
             "toolsUsage-codeLocaleComment": "Mevcut çevirilere ve aktif dile erişin",
+            "markdownEditor-title": "Markdown Düzenleyici (Markdown Editor)",
             "markdownViewer-title": "Markdown Görüntüleyici (Markdown Viewer)",
             "typesOverride-tsconfigIntegration": "tsconfig.json Entegrasyonu",
             "bottomSheet-toggleSwipeClose": "Kaydırarak Kapatmayı Aç/Kapat",
@@ -423,6 +425,7 @@
             "siteLogo-desc": "A component that renders the main logo image representing the brand or application.",
             "timeSelector-desc": "An interface component optimized for selecting a specific time (hour, minute).",
             "menu-desc": "A general-purpose menu component that presents various options and links to the user.",
+            "markdownEditor-desc": "A content component that allows you to edit rich text in Markdown format.",
             "chip-desc": "A compact component used to display small pieces of information, tags, or statuses.",
             "localeExamples-desc": "Switching between locales and dynamic text usage with localize function.",
             "highlightButton-desc": "An eye-catching button component used to highlight important actions.",
@@ -558,6 +561,7 @@
             "checkBox-enterSubText": "Enter Sub Text",
             "highlightButton-enterText": "Enter Text",
             "localeExamples-title": "Locale Examples",
+            "markdownEditor-title": "Markdown Editor",
             "markdownViewer-title": "Markdown Viewer",
             "radioButton-exampleTitle": "Radio Title",
             "rowCard-enterSubTitle": "Enter Subtitle",
@@ -755,4 +759,4 @@
         "locale": "en-US",
         "isRTL": false
     }
-]
+]

+ 4 - 4
src/components/checkBox/index.tsx

@@ -17,8 +17,8 @@ import {
 } from "../../core/hooks";
 import type ITextProps from "../text/type";
 import {
-    Check,
-    Minus
+    Check as CheckIcon,
+    Minus as MinusIcon
 } from "lucide-react-native";
 import Loading from "../loading";
 import Text from "../text";
@@ -166,13 +166,13 @@ const CheckBox: FC<ICheckBoxProps> = ({
         const selectedColor = indicatorIconProps.customColor ? indicatorIconProps.customColor : colors.content.border[indicatorIconProps.color];
 
         if(isChecked === "partially") {
-            return <Minus
+            return <MinusIcon
                 color={selectedColor}
                 size={16}
             />;
         }
 
-        return <Check
+        return <CheckIcon
             color={selectedColor}
             size={16}
         />;

+ 4 - 4
src/components/embeddedMenu/index.tsx

@@ -33,8 +33,8 @@ import type {
     RefForwardingComponent
 } from "../../types";
 import {
-    PanelLeftClose,
-    PanelLeftOpen
+    PanelLeftClose as PanelLeftCloseIcon,
+    PanelLeftOpen as PanelLeftOpenIcon
 } from "lucide-react-native";
 import {
     SafeAreaView
@@ -256,7 +256,7 @@ const Menu: RefForwardingComponent<IEmbeddedMenuRef, IEmbeddedMenuProps> = ({
                 color,
                 size
             }) => {
-                return <PanelLeftOpen
+                return <PanelLeftOpenIcon
                     color={colors.content.icon[color]}
                     size={size * 2}
                 />;
@@ -288,7 +288,7 @@ const Menu: RefForwardingComponent<IEmbeddedMenuRef, IEmbeddedMenuProps> = ({
                 color,
                 size
             }) => {
-                return <PanelLeftClose
+                return <PanelLeftCloseIcon
                     color={colors.content.icon[color]}
                     size={size * 2}
                 />;

+ 10 - 0
src/components/index.ts

@@ -420,3 +420,13 @@ export type {
     default as IYearSelectorProps,
     IYearSelectorRef
 } from "./yearSelector/type";
+
+export {
+    default as MarkdownEditor
+} from "./markdownEditor";
+
+export type {
+    default as IMarkdownEditorProps,
+    MarkdownDynamicStyles,
+    MarkdownEditorMode
+} from "./markdownEditor/type";

+ 531 - 0
src/components/markdownEditor/index.tsx

@@ -0,0 +1,531 @@
+import {
+    useState,
+    useRef,
+    type FC
+} from "react";
+import {
+    TouchableOpacity,
+    ScrollView,
+    TextInput,
+    View
+} from "react-native";
+import type {
+    MarkdownEditorMode
+} from "./type";
+import type IMarkdownEditorProps from "./type";
+import stylesheet, {
+    useStyles
+} from "./stylesheet";
+import {
+    NCoreUIKitLocalize,
+    NCoreUIKitTheme
+} from "../../core/hooks";
+import type {
+    TextMarkdownTypes,
+    MarkdownKeys
+} from "../markdownViewer/type";
+import {
+    ChevronsDown as ChevronsDownIcon,
+    ChevronDown as ChevronDownIcon,
+    AlignCenter as AlignCenterIcon,
+    AlignRight as AlignRightIcon,
+    AlignLeft as AlignLeftIcon,
+    Italic as ItalicIcon,
+    Quote as QuoteIcon,
+    Image as ImageIcon,
+    Bold as BoldIcon,
+    Link as LinkIcon,
+    Code as CodeIcon,
+    List as ListIcon
+} from "lucide-react-native";
+import MarkdownViewer from "../markdownViewer";
+import {
+    DEFAULT_VARIANTS
+} from "../markdownViewer/constant";
+import Text from "../text";
+import {
+    webStyle
+} from "../../utils";
+
+const MarkdownEditor: FC<IMarkdownEditorProps> = ({
+    spreadBehaviour = "stretch",
+    isViewerResizable = true,
+    isEditorResizable = true,
+    toolbarContainerStyle,
+    defaultMode = "live",
+    initialValue = "",
+    onChangeText,
+    variants,
+    style,
+    ...props
+}) => {
+    const {
+        radiuses,
+        borders,
+        colors,
+        spaces
+    } = NCoreUIKitTheme.useContext();
+
+    const {
+        localize
+    } = NCoreUIKitLocalize.useContext();
+
+    const [
+        text,
+        setText
+    ] = useState<string>(initialValue);
+
+    const [
+        mode,
+        setMode
+    ] = useState<MarkdownEditorMode>(defaultMode);
+
+    const [
+        selection,
+        setSelection
+    ] = useState<{ end: number; start: number } | null>(null);
+
+    const [
+        isAutoScrollEnabled,
+        setIsAutoScrollEnabled
+    ] = useState<boolean>(true);
+
+    const previewScrollRef = useRef<ScrollView>(null);
+
+    const dynamicStyles = useStyles({
+        spreadBehaviour,
+        radiuses,
+        borders,
+        colors,
+        spaces
+    });
+
+    const handleAction = (prefix: string, suffix: string, placeholder: string) => {
+        let newText = "";
+
+        if (selection && selection.start !== selection.end) {
+            const before = text.substring(0, selection.start);
+            const selected = text.substring(selection.start, selection.end);
+            const after = text.substring(selection.end);
+
+            const hasPrefix = prefix !== "" && selected.startsWith(prefix);
+            const hasSuffix = suffix !== "" && selected.endsWith(suffix);
+
+            const hasOuterPrefix = prefix !== "" && before.endsWith(prefix);
+            const hasOuterSuffix = suffix !== "" && after.startsWith(suffix);
+
+            if (
+                (prefix === "" || hasPrefix) &&
+                (suffix === "" || hasSuffix) &&
+                (prefix !== "" || suffix !== "")
+            ) {
+                const stripped = selected.substring(prefix.length, selected.length - suffix.length);
+                newText = before + stripped + after;
+            } else if (
+                (prefix === "" || hasOuterPrefix) &&
+                (suffix === "" || hasOuterSuffix) &&
+                (prefix !== "" || suffix !== "")
+            ) {
+                const newBefore = before.substring(0, before.length - prefix.length);
+                const newAfter = after.substring(suffix.length);
+                newText = newBefore + selected + newAfter;
+            } else {
+                newText = before + prefix + selected + suffix + after;
+            }
+        } else {
+            const insertPos = selection ? selection.start : text.length;
+            const before = text.substring(0, insertPos);
+            const after = text.substring(insertPos);
+            newText = before + prefix + placeholder + suffix + after;
+        }
+
+        setText(newText);
+        onChangeText?.(newText);
+    };
+
+    const handleTypographySelect = (syntax: string) => {
+        let prefix = syntax;
+        let suffix = "";
+
+        if (syntax === "<p>") {
+            prefix = "<p>";
+            suffix = "</p>";
+        }
+
+        if (selection && selection.start !== selection.end) {
+            const before = text.substring(0, selection.start);
+            const selected = text.substring(selection.start, selection.end);
+            const after = text.substring(selection.end);
+
+            const prependNewline = before.length > 0 && !before.endsWith("\n");
+            const finalPrefix = (prependNewline ? "\n" : "") + prefix;
+
+            const appendNewline = after.length > 0 && !after.startsWith("\n");
+            const finalSuffix = suffix + (appendNewline ? "\n" : "");
+
+            const newText = before + finalPrefix + selected + finalSuffix + after;
+
+            setText(newText);
+            onChangeText?.(newText);
+        } else {
+            const insertPos = selection ? selection.start : text.length;
+            const before = text.substring(0, insertPos);
+            const after = text.substring(insertPos);
+
+            const prependNewline = before.length > 0 && !before.endsWith("\n");
+            const finalPrefix = (prependNewline ? "\n" : "") + prefix;
+
+            const newText = before + finalPrefix + suffix + after;
+
+            setText(newText);
+            onChangeText?.(newText);
+        }
+    };
+
+    const renderToolbar = () => {
+        const activeVariants = {
+            ...DEFAULT_VARIANTS,
+            ...variants
+        };
+
+        const textSyntaxKeys: TextMarkdownTypes[] = [
+            "# ",
+            "## ",
+            "### ",
+            "#### ",
+            "##### ",
+            "###### ",
+            "<p>"
+        ];
+        const typographyKeysToRender = textSyntaxKeys.map(syntax => ({
+            syntax,
+            typographyKey: activeVariants[syntax as MarkdownKeys]
+        })).filter(item => item.typographyKey);
+
+        return <View
+            style={[
+                stylesheet.toolbarContainer,
+                dynamicStyles.toolbarContainer,
+                toolbarContainerStyle
+            ]}
+        >
+            <TouchableOpacity
+                onPress={() => handleAction("**", "**", "bold")}
+                style={[
+                    stylesheet.toolbarAction,
+                    dynamicStyles.toolbarAction
+                ]}
+            >
+                <BoldIcon
+                    color={colors.content.icon.mid}
+                    size={18}
+                />
+            </TouchableOpacity>
+            <TouchableOpacity
+                onPress={() => handleAction("__", "__", "italic")}
+                style={[
+                    stylesheet.toolbarAction,
+                    dynamicStyles.toolbarAction
+                ]}
+            >
+                <ItalicIcon
+                    color={colors.content.icon.mid}
+                    size={18}
+                />
+            </TouchableOpacity>
+            <View
+                style={[
+                    stylesheet.toolbarSeparator,
+                    dynamicStyles.toolbarSeparator
+                ]}
+            />
+            <TouchableOpacity
+                onPress={() => handleAction("<link href=\"https://nibgat.com\">\n", "\n</link>", "title")}
+                style={[
+                    stylesheet.toolbarAction,
+                    dynamicStyles.toolbarAction
+                ]}
+            >
+                <LinkIcon
+                    color={colors.content.icon.mid}
+                    size={18}
+                />
+            </TouchableOpacity>
+            <TouchableOpacity
+                onPress={() => handleAction("[", "](https://nibgat.com/assets/images/logo.png)", "alt")}
+                style={[
+                    stylesheet.toolbarAction,
+                    dynamicStyles.toolbarAction
+                ]}
+            >
+                <ImageIcon
+                    color={colors.content.icon.mid}
+                    size={18}
+                />
+            </TouchableOpacity>
+            <TouchableOpacity
+                onPress={() => handleAction("```\n", "\n```", "code")}
+                style={[
+                    stylesheet.toolbarAction,
+                    dynamicStyles.toolbarAction
+                ]}
+            >
+                <CodeIcon
+                    color={colors.content.icon.mid}
+                    size={18}
+                />
+            </TouchableOpacity>
+            <TouchableOpacity
+                onPress={() => handleAction("\n* ", "", "list item")}
+                style={[
+                    stylesheet.toolbarAction,
+                    dynamicStyles.toolbarAction
+                ]}
+            >
+                <ListIcon
+                    color={colors.content.icon.mid}
+                    size={18}
+                />
+            </TouchableOpacity>
+            <TouchableOpacity
+                onPress={() => handleAction("\n<<\n", "\n>>\n", "quote")}
+                style={[
+                    stylesheet.toolbarAction,
+                    dynamicStyles.toolbarAction
+                ]}
+            >
+                <QuoteIcon
+                    color={colors.content.icon.mid}
+                    size={18}
+                />
+            </TouchableOpacity>
+            <View
+                style={[
+                    stylesheet.toolbarSeparator,
+                    dynamicStyles.toolbarSeparator
+                ]}
+            />
+            <TouchableOpacity
+                onPress={() => handleAction("<left>\n", "\n</left>", "content")}
+                style={[
+                    stylesheet.toolbarAction,
+                    dynamicStyles.toolbarAction
+                ]}
+            >
+                <AlignLeftIcon
+                    color={colors.content.icon.mid}
+                    size={18}
+                />
+            </TouchableOpacity>
+            <TouchableOpacity
+                onPress={() => handleAction("<center>\n", "\n</center>", "content")}
+                style={[
+                    stylesheet.toolbarAction,
+                    dynamicStyles.toolbarAction
+                ]}
+            >
+                <AlignCenterIcon
+                    color={colors.content.icon.mid}
+                    size={18}
+                />
+            </TouchableOpacity>
+            <TouchableOpacity
+                onPress={() => handleAction("<right>\n", "\n</right>", "content")}
+                style={[
+                    stylesheet.toolbarAction,
+                    dynamicStyles.toolbarAction
+                ]}
+            >
+                <AlignRightIcon
+                    color={colors.content.icon.mid}
+                    size={18}
+                />
+            </TouchableOpacity>
+            <View
+                style={[
+                    stylesheet.toolbarSeparator,
+                    dynamicStyles.toolbarSeparator
+                ]}
+            />
+
+            <ScrollView
+                horizontal
+                showsHorizontalScrollIndicator={false}
+                style={stylesheet.typographyScroll}
+            >
+                {typographyKeysToRender.map((item) => {
+                    return <TouchableOpacity
+                        key={item.typographyKey}
+                        style={[
+                            stylesheet.typographyPill,
+                            dynamicStyles.typographyPill
+                        ]}
+                        onPress={() => handleTypographySelect(item.syntax)}
+                    >
+                        <Text
+                            color="mid"
+                        >
+                            {localize(`typography-${item.typographyKey}` as Parameters<typeof localize>[0])}
+                        </Text>
+                    </TouchableOpacity>;
+                })}
+            </ScrollView>
+            <View
+                style={stylesheet.switchContainer}
+            >
+                <TouchableOpacity
+                    onPress={() => setIsAutoScrollEnabled(!isAutoScrollEnabled)}
+                    style={[
+                        stylesheet.toolbarAction,
+                        dynamicStyles.toolbarAction,
+                        {
+                            backgroundColor: isAutoScrollEnabled ? colors.content.container.primary : "transparent"
+                        }
+                    ]}
+                >
+                    {
+                        isAutoScrollEnabled ?
+                            <ChevronsDownIcon
+                                color={colors.content.text.constrastHigh}
+                                size={18}
+                            />
+                        :
+                            <ChevronDownIcon
+                                color={colors.content.icon.mid}
+                                size={18}
+                            />
+                    }
+                </TouchableOpacity>
+            </View>
+        </View>;
+    };
+
+    return <View
+        style={[
+            stylesheet.container,
+            dynamicStyles.container,
+            style
+        ]}
+    >
+        <View
+            style={[
+                stylesheet.tabsContainer,
+                dynamicStyles.tabsContainer
+            ]}
+        >
+            <TouchableOpacity
+                style={[
+                    stylesheet.tabButton,
+                    mode === "live" ? dynamicStyles.tabButtonActive : dynamicStyles.tabButtonInactive
+                ]}
+                onPress={() => setMode("live")}
+            >
+                <Text
+                    customColor={mode === "live" ? colors.content.text.emphasized : undefined}
+                    color={mode === "live" ? undefined : "mid"}
+                >
+                    {localize("live")}
+                </Text>
+            </TouchableOpacity>
+            <TouchableOpacity
+                style={[
+                    stylesheet.tabButton,
+                    mode === "write" ? dynamicStyles.tabButtonActive : dynamicStyles.tabButtonInactive
+                ]}
+                onPress={() => setMode("write")}
+            >
+                <Text
+                    customColor={mode === "write" ? colors.content.text.emphasized : undefined}
+                    color={mode === "write" ? undefined : "mid"}
+                >
+                    {localize("write")}
+                </Text>
+            </TouchableOpacity>
+            <TouchableOpacity
+                style={[
+                    stylesheet.tabButton,
+                    mode === "preview" ? dynamicStyles.tabButtonActive : dynamicStyles.tabButtonInactive
+                ]}
+                onPress={() => setMode("preview")}
+            >
+                <Text
+                    customColor={mode === "preview" ? colors.content.text.emphasized : undefined}
+                    color={mode === "preview" ? undefined : "mid"}
+                >
+                    {localize("preview")}
+                </Text>
+            </TouchableOpacity>
+        </View>
+        {
+            (mode === "live" || mode === "preview") &&
+                <View
+                    style={[
+                        stylesheet.previewContainer,
+                        dynamicStyles.previewContainer,
+                        mode === "live" ? {
+                            marginBottom: 10,
+                            minHeight: 100,
+                            height: 250,
+                            overflow: "hidden",
+                            ...(isViewerResizable ? webStyle({
+                                resize: "vertical"
+                            }) : {})
+                        } : {}
+                    ]}
+                >
+                    {
+                        mode === "live" ?
+                            <ScrollView
+                                ref={previewScrollRef}
+                                onContentSizeChange={() => {
+                                    if (isAutoScrollEnabled) {
+                                        previewScrollRef.current?.scrollToEnd({
+                                            animated: true
+                                        });
+                                    }
+                                }}
+                            >
+                                <MarkdownViewer
+                                    variants={variants}
+                                    content={text}
+                                />
+                            </ScrollView>
+                        :
+                            <MarkdownViewer
+                                variants={variants}
+                                content={text}
+                            />
+                    }
+                </View>
+        }
+        {
+            (mode === "live" || mode === "write") &&
+                <View>
+                    {renderToolbar()}
+                    <TextInput
+                        {...props}
+                        multiline={true}
+                        value={text}
+                        placeholderTextColor={colors.content.text.mid}
+                        onSelectionChange={(e) => {
+                            setSelection(e.nativeEvent.selection);
+                            props.onSelectionChange?.(e);
+                        }}
+                        onChangeText={(val) => {
+                            setText(val);
+                            onChangeText?.(val);
+                        }}
+                        style={[
+                            stylesheet.editorInput,
+                            dynamicStyles.editorInput,
+                            isEditorResizable ? webStyle({
+                                resize: "vertical"
+                            }) : {},
+                            style
+                        ]}
+                    />
+                </View>
+        }
+    </View>;
+};
+export default MarkdownEditor;

+ 140 - 0
src/components/markdownEditor/stylesheet.ts

@@ -0,0 +1,140 @@
+import {
+    type ViewStyle,
+    StyleSheet
+} from "react-native";
+import type {
+    MarkdownDynamicStyles
+} from "./type";
+import type {
+    Mutable
+} from "../../types";
+
+const stylesheet = StyleSheet.create({
+    typographyScroll: {
+        paddingHorizontal: 8,
+        flexDirection: "row"
+    },
+    toolbarContainer: {
+        borderBottomWidth: 0,
+        paddingHorizontal: 8,
+        flexDirection: "row",
+        alignItems: "center",
+        paddingVertical: 4,
+        flexWrap: "wrap"
+    },
+    toolbarSeparator: {
+        marginHorizontal: 6,
+        height: 18
+    },
+    previewContainer: {
+        minHeight: 150
+    },
+    typographyPill: {
+        justifyContent: "center",
+        paddingHorizontal: 10,
+        alignItems: "center",
+        paddingVertical: 4,
+        marginRight: 6,
+        borderWidth: 0
+    },
+    toolbarAction: {
+        justifyContent: "center",
+        alignItems: "center",
+        marginHorizontal: 2,
+        padding: 6
+    },
+    tabsContainer: {
+        flexDirection: "row"
+    },
+    switchContainer: {
+        justifyContent: "center",
+        marginLeft: "auto",
+        paddingRight: 8
+    },
+    editorInput: {
+        textAlignVertical: "top",
+        minHeight: 100,
+        height: 250
+    },
+    tabButton: {
+        paddingHorizontal: 16,
+        paddingVertical: 10
+    },
+    container: {
+        width: "100%"
+    }
+});
+
+export const useStyles = ({
+    spreadBehaviour,
+    radiuses,
+    borders,
+    colors,
+    spaces
+}: MarkdownDynamicStyles) => {
+    const styles = {
+        container: {
+            alignSelf: "auto"
+        } as Mutable<ViewStyle>,
+        tabButtonInactive: {
+            borderBottomColor: "transparent"
+        },
+        toolbarContainer: {
+            backgroundColor: colors.content.container.subtle,
+            borderColor: colors.content.border.subtle,
+            borderTopRightRadius: radiuses.form,
+            borderTopLeftRadius: radiuses.form,
+            borderWidth: borders.line
+        },
+        toolbarSeparator: {
+            backgroundColor: colors.content.border.subtle,
+            width: borders.line
+        },
+        toolbarAction: {
+            borderRadius: radiuses.actions
+        },
+        previewContainer: {
+            backgroundColor: colors.content.container.default,
+            borderColor: colors.content.border.subtle,
+            borderRadius: radiuses.form,
+            borderWidth: borders.line,
+            padding: spaces.spacingMd
+        },
+        tabButtonActive: {
+            borderBottomColor: colors.content.container.primary
+        },
+        tabButton: {
+            borderBottomWidth: borders.indicator
+        },
+        typographyPill: {
+            backgroundColor: colors.content.container.subtle,
+            borderColor: colors.content.border.subtle,
+            borderRadius: radiuses.chip
+        },
+        tabsContainer: {
+            borderBottomColor: colors.content.border.subtle,
+            borderBottomWidth: borders.line,
+            marginBottom: spaces.spacingSm
+        },
+        editorInput: {
+            backgroundColor: colors.content.container.default,
+            borderColor: colors.content.border.subtle,
+            borderBottomRightRadius: radiuses.form,
+            borderBottomLeftRadius: radiuses.form,
+            color: colors.content.text.high,
+            borderWidth: borders.line,
+            padding: spaces.spacingMd
+        }
+    };
+
+    if (spreadBehaviour === "baseline") {
+        styles.container.alignSelf = spreadBehaviour;
+        styles.container.width = "auto";
+    } else if (spreadBehaviour === "stretch") {
+        styles.container.alignSelf = spreadBehaviour;
+        styles.container.width = "100%";
+    }
+
+    return styles;
+};
+export default stylesheet;

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

@@ -0,0 +1,34 @@
+import type {
+    TextInputProps,
+    StyleProp,
+    ViewStyle
+} from "react-native";
+import type {
+    MarkdownKeys
+} from "../markdownViewer/type";
+
+export type MarkdownEditorMode = "live" | "write" | "preview";
+
+export type MarkdownEditorSpreadBehaviour = "baseline" | "stretch" | "free";
+
+export type MarkdownDynamicStyles = {
+    spreadBehaviour?: MarkdownEditorSpreadBehaviour;
+    radiuses: NCoreUIKit.ActivePalette["radiuses"];
+    borders: NCoreUIKit.ActivePalette["borders"];
+    colors: NCoreUIKit.ActivePalette["colors"];
+    spaces: NCoreUIKit.ActivePalette["spaces"];
+};
+
+interface IMarkdownEditorProps extends TextInputProps {
+    variants?: Partial<Record<MarkdownKeys, keyof NCoreUIKit.Typography>>;
+    spreadBehaviour?: MarkdownEditorSpreadBehaviour;
+    toolbarContainerStyle?: StyleProp<ViewStyle>;
+    defaultMode?: MarkdownEditorMode;
+    isViewerResizable?: boolean;
+    isEditorResizable?: boolean;
+    initialValue?: string;
+};
+
+export type {
+    IMarkdownEditorProps as default
+};

+ 89 - 86
src/components/markdownViewer/index.tsx

@@ -1,5 +1,4 @@
 import {
-    useEffect,
     useState,
     type FC
 } from "react";
@@ -7,14 +6,15 @@ import {
     type DimensionValue,
     Text as NativeText,
     TouchableOpacity,
+    type ImageProps,
     Linking,
     Image,
     View
 } from "react-native";
 import type IMarkdownViewerProps from "./type";
 import type {
-    MarkdownObject,
-    TextMarkdownTypes
+    TextMarkdownTypes,
+    MarkdownObject
 } from "./type";
 import stylesheet, {
     useStyles
@@ -29,11 +29,74 @@ import type ITextProps from "../text/type";
 import {
     Quote as QuoteIcon
 } from "lucide-react-native";
+import CodeViewer from "../codeViewer";
 import Text from "../text";
 
+const MarkdownImage: FC<{
+    containerWidth: number | null;
+    imageProps?: ImageProps;
+    node: MarkdownObject;
+}> = ({
+    containerWidth,
+    imageProps,
+    node
+}) => {
+    const [imgSize,
+        setImgSize] = useState<{
+        height: number | string;
+        width: number | string;
+        isSetted: boolean;
+    }>({
+        isSetted: false,
+        width: "100%",
+        height: 0
+    });
+
+    const targetWidth = node.size ? node.size[0] : imgSize.width;
+    const targetHeight = node.size ? node.size[1] : imgSize.height;
+
+    const currentWidth = typeof targetWidth === "number" || (typeof targetWidth === "string" && targetWidth.indexOf("%") === -1) || !containerWidth
+        ? targetWidth
+        : (containerWidth * Number((targetWidth as string).split("%")[0])) / 100;
+
+    const currentHeight = typeof targetHeight === "number" || (typeof targetHeight === "string" && targetHeight.indexOf("%") === -1) || !containerWidth
+        ? targetHeight
+        : (containerWidth * Number((targetHeight as string).split("%")[0])) / 100;
+
+    return <Image
+        {...imageProps}
+        alt={node.content}
+        source={{
+            uri: node.url
+        }}
+        resizeMode={node.resizeMode ? node.resizeMode : "contain"}
+        onLoad={(event) => {
+            if (!node.size) {
+                const nativeEvent = event.nativeEvent as unknown as {
+                    source?: { height: number; width: number };
+                    height?: number;
+                    width?: number;
+                };
+                const height = nativeEvent.source?.height || nativeEvent.height || 0;
+                const width = nativeEvent.source?.width || nativeEvent.width || 0;
+                setImgSize({
+                    isSetted: true,
+                    height,
+                    width
+                });
+            }
+        }}
+        style={{
+            ...stylesheet.image,
+            height: currentHeight as DimensionValue,
+            width: currentWidth as DimensionValue,
+            ...imageProps?.style
+        }}
+    />;
+};
+
 const MarkdownViewer: FC<IMarkdownViewerProps> = ({
     blockquoteIconSize = "titleLargeSize",
-    codeTextColor = "emphasized",
     blockquoteIconColor = "mid",
     blockquoteTextColor = "mid",
     blockquoteContainerProps,
@@ -55,11 +118,11 @@ const MarkdownViewer: FC<IMarkdownViewerProps> = ({
     enterTextProps,
     boldTextProps,
     listIconProps,
-    codeTextProps,
     listTextProps,
     italicStyle,
     imageProps,
     boldStyle,
+    variants,
     content,
     style,
     ...props
@@ -78,8 +141,7 @@ const MarkdownViewer: FC<IMarkdownViewerProps> = ({
 
     const {
         blockquoteContainer: blockquoteContainerDynamicStyle,
-        blockquoteIcon: blockquoteIconDynamicStyle,
-        codeContainer: codeContainerDynamicStyle
+        blockquoteIcon: blockquoteIconDynamicStyle
     } = useStyles({
         radiuses,
         colors,
@@ -150,7 +212,7 @@ const MarkdownViewer: FC<IMarkdownViewerProps> = ({
         node: MarkdownObject
     }) => {
         let textProps: ITextProps = {
-            variant: node.variant
+            variant: (variants as Record<string, keyof NCoreUIKit.Typography>)?.[node.nativeType] || node.variant
         };
 
         if(customTextStyles && customTextStyles[node.nativeType as TextMarkdownTypes]) {
@@ -187,7 +249,7 @@ const MarkdownViewer: FC<IMarkdownViewerProps> = ({
             style={[
                 stylesheet.enter,
                 {
-                    height: typography[node.variant as keyof NCoreUIKit.Typography].fontSize
+                    height: typography[((variants as Record<string, keyof NCoreUIKit.Typography>)?.[node.nativeType] || node.variant) as keyof NCoreUIKit.Typography].fontSize
                 },
                 enterTextProps?.style
             ]}
@@ -217,7 +279,7 @@ const MarkdownViewer: FC<IMarkdownViewerProps> = ({
             />
             <Text
                 color={blockquoteTextColor}
-                variant={node.variant}
+                variant={(variants as Record<string, keyof NCoreUIKit.Typography>)?.[node.nativeType] || node.variant}
                 style={{
                     ...stylesheet.blockquoteText,
                     ...blockquoteTextStyle
@@ -228,72 +290,16 @@ const MarkdownViewer: FC<IMarkdownViewerProps> = ({
         </View>;
     };
 
-    const renderImage = ({
+    const renderImageComponent = ({
         node
     }: {
         node: MarkdownObject
     }) => {
-        const [
-            imgSize,
-            setImgSize
-        ] = useState<{
-            height: number | string;
-            width: number | string;
-            isSetted: boolean;
-        }>({
-            isSetted: false,
-            width: "100%",
-            height: 0
-        });
-
-        useEffect(() => {
-            if(!imgSize.isSetted && node.size) {
-                setImgSize({
-                    height: node.size[1] as number | string,
-                    width: node.size[0] as number | string,
-                    isSetted: true
-                });
-            }
-        }, [
-            imgSize
-        ]);
-
-        const currentWidth = typeof imgSize.width === "number" || imgSize.width.indexOf("%") === -1 || !containerWidth
-            ? imgSize.width
-            : (containerWidth * Number(imgSize.width.split("%")[0])) / 100;
-
-        const currentHeight = typeof imgSize.height === "number" || imgSize.height.indexOf("%") === -1 || !containerWidth
-            ? imgSize.height
-            : (containerWidth * Number(imgSize.height.split("%")[0])) / 100;
-
-        return <Image
-            {...imageProps}
-            alt={node.content}
+        return <MarkdownImage
+            node={node}
+            imageProps={imageProps}
+            containerWidth={containerWidth}
             key={node.key}
-            source={{
-                uri: node.url
-            }}
-            resizeMode={node.resizeMode ? node.resizeMode : "contain"}
-            onLoad={(event) => {
-                if(!node.size) {
-                    const {
-                        height,
-                        width
-                    } = event.nativeEvent.source;
-
-                    setImgSize({
-                        isSetted: true,
-                        height,
-                        width
-                    });
-                }
-            }}
-            style={{
-                ...stylesheet.image,
-                height: currentHeight as DimensionValue,
-                width: currentWidth as DimensionValue,
-                ...imageProps?.style
-            }}
         />;
     };
 
@@ -307,18 +313,21 @@ const MarkdownViewer: FC<IMarkdownViewerProps> = ({
             key={node.key}
             style={[
                 stylesheet.listContainer,
-                listContainerProps?.style
+                listContainerProps?.style,
+                {
+                    paddingLeft: (node.indent || 0) * 8
+                }
             ]}
         >
             <Text
                 {...listIconProps}
-                variant={node.variant}
+                variant={(variants as Record<string, keyof NCoreUIKit.Typography>)?.[node.nativeType] || node.variant}
             >
                 •{" "}
             </Text>
             <Text
                 {...listTextProps}
-                variant={node.variant}
+                variant={(variants as Record<string, keyof NCoreUIKit.Typography>)?.[node.nativeType] || node.variant}
             >
                 {renderInlineStyles(node.content)}
             </Text>
@@ -333,18 +342,12 @@ const MarkdownViewer: FC<IMarkdownViewerProps> = ({
         return <View
             {...codeContainerProps}
             key={node.key}
-            style={[
-                codeContainerDynamicStyle,
-                codeContainerProps?.style
-            ]}
+            style={codeContainerProps?.style}
         >
-            <Text
-                {...codeTextProps}
-                variant={node.variant}
-                color={codeTextColor}
-            >
-                {node.content}
-            </Text>
+            <CodeViewer
+                language={node.language}
+                code={node.content}
+            />
         </View>;
     };
 
@@ -489,7 +492,7 @@ const MarkdownViewer: FC<IMarkdownViewerProps> = ({
                     });
                 }
                 case "image": {
-                    return renderImage({
+                    return renderImageComponent({
                         node
                     });
                 }

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

@@ -59,11 +59,6 @@ export const useStyles = ({
         },
         blockquoteIcon: {
             marginBottom: spaces.spacingXs
-        },
-        codeContainer: {
-            backgroundColor: colors.content.container.mid,
-            borderRadius: radiuses.form,
-            padding: spaces.spacingMd
         }
     };
 

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

@@ -21,6 +21,8 @@ export type MarkdownObject = {
     nativeType: MarkdownKeys | "image" | "center" | "right" | "left" | "link";
     variant?: keyof NCoreUIKit.Typography;
     size?: Array<number | string>;
+    language?: string;
+    indent?: number;
     content: string;
     url?: string;
     key: number;
@@ -45,11 +47,11 @@ export type ListTypes = "* " | "- ";
 export type MarkdownKeys = TextMarkdownTypes | EnterMarkdownTypes | CodeMarkdownTypes | BlockquoteTypes | ListTypes;
 
 interface IMarkdownViewerProps extends ViewProps {
+    variants?: Partial<Record<MarkdownKeys, keyof NCoreUIKit.Typography>>;
     blockquoteIconStyle?: StyleProp<ViewStyle>[] | StyleProp<ViewStyle>;
     blockquoteTextColor?: keyof NCoreUIKit.TextContentColors;
     blockquoteIconColor?: keyof NCoreUIKit.IconContentColors;
     style?: StyleProp<ViewStyle>[] | StyleProp<ViewStyle>;
-    codeTextColor?: keyof NCoreUIKit.TextContentColors;
     blockquoteIconSize?: keyof NCoreUIKit.Typography;
     linkContainerProps?: TouchableOpacityProps;
     centerMarkdownProps?: IMarkdownViewerProps;
@@ -65,7 +67,6 @@ interface IMarkdownViewerProps extends ViewProps {
     listContainerProps?: ViewProps;
     italicTextProps?: TextProps;
     enterTextProps?: ITextProps;
-    codeTextProps?: ITextProps;
     listTextProps?: ITextProps;
     listIconProps?: ITextProps;
     boldTextProps?: TextProps;
@@ -78,8 +79,8 @@ interface IMarkdownViewerProps extends ViewProps {
         TextMarkdownTypes,
         ITextProps["style"]
     >;
-    italicStyle?: TextStyle;
     boldStyle?: TextStyle;
+    italicStyle?: TextStyle;
     content: string;
 };
 export type {

+ 32 - 10
src/components/markdownViewer/util.ts

@@ -49,12 +49,17 @@ export const textMarkdown = ({
     index: number;
     line: string;
 }): MarkdownObject => {
+    let content = line.replace(type, "");
+    if (type === "<p>") {
+        content = content.replace("</p>", "");
+    }
+
     return {
-        content: line.replace(type, ""),
         variant: DEFAULT_VARIANTS[type],
         nativeType: type,
         type: "text",
-        key: index
+        key: index,
+        content
     };
 };
 
@@ -101,7 +106,11 @@ export const codeMarkdown = ({
 
         const codeContent = lines.slice(index + 1, endIndex).join("\n");
 
+        const languageMatch = lines[index]?.match(/^```(.*)/);
+        const language = languageMatch && languageMatch[1] ? languageMatch[1].trim() : "";
+
         return {
+            language: language !== "" ? language : undefined,
             variant: DEFAULT_VARIANTS["```"],
             content: codeContent,
             nativeType: "```",
@@ -292,19 +301,22 @@ export const blockquoteMarkdown = ({
 
 export const listMarkdown = ({
     isStarList,
+    indent,
     index,
     line
 }: {
     isStarList: boolean;
+    indent: number;
     index: number;
     line: string;
 }): MarkdownObject => {
     return {
         variant: DEFAULT_VARIANTS[isStarList ? "* " : "- "],
         nativeType: isStarList ? "* " : "- ",
-        content: line.substring(2),
+        content: line,
         type: "list",
-        key: index
+        key: index,
+        indent
     };
 };
 
@@ -358,6 +370,12 @@ export const parseMarkdown = (rawText: string): Array<MarkdownObject> => {
             line
         });
 
+        if (line.startsWith("<p>")) return textMarkdown({
+            type: "<p>",
+            index,
+            line
+        });
+
         if (line.startsWith("<br/>") || line === "") return enterMarkdown({
             index,
             line
@@ -434,13 +452,17 @@ export const parseMarkdown = (rawText: string): Array<MarkdownObject> => {
             line
         });
 
-        const isStarList = line.startsWith("* ");
+        const listMatch = line.match(/^(\s*)(?:\*|-)\s(.*)/);
 
-        if (isStarList || line.startsWith("- ")) return listMarkdown({
-            isStarList,
-            index,
-            line
-        });
+        if (listMatch) {
+            const isStarList = line.trim().startsWith("*");
+            return listMarkdown({
+                indent: listMatch[1]?.length ?? 0,
+                line: listMatch[2] ?? "",
+                isStarList,
+                index
+            });
+        }
 
         return {
             variant: DEFAULT_VARIANTS["<p>"],

+ 15 - 6
src/components/textAreaInput/index.tsx

@@ -156,9 +156,13 @@ const TextAreaInput: RefForwardingComponent<ITextAreaInputRef, ITextAreaInputPro
 
     useEffect(() => {
         if(initialValue) {
-            inputRef.current?.setNativeProps({
-                text: initialValue
-            });
+            if (Platform.OS !== "web") {
+                inputRef.current?.setNativeProps({
+                    text: initialValue
+                });
+            } else if (inputRef.current) {
+                (inputRef.current as unknown as { value: string }).value = initialValue;
+            }
         }
     }, []);
 
@@ -208,9 +212,13 @@ const TextAreaInput: RefForwardingComponent<ITextAreaInputRef, ITextAreaInputPro
     const updateValue = (text: string) => {
         setValue(text);
 
-        inputRef.current?.setNativeProps({
-            text: text
-        });
+        if (Platform.OS !== "web") {
+            inputRef.current?.setNativeProps({
+                text: text
+            });
+        } else if (inputRef.current) {
+            (inputRef.current as unknown as { value: string }).value = text;
+        }
     };
 
     const onFocus = () => {
@@ -451,6 +459,7 @@ const TextAreaInput: RefForwardingComponent<ITextAreaInputRef, ITextAreaInputPro
             <NativeTextInput
                 {...props}
                 placeholderTextColor={colors.content.text[currentType.placeholderColor]}
+                value={isUpdateOnRealtime ? value : undefined}
                 underlineColorAndroid="rgba(255,255,255,0)"
                 placeholder={placeholder}
                 allowFontScaling={false}

+ 6 - 6
src/components/textAreaInput/type.ts

@@ -66,26 +66,27 @@ interface ITextAreaInputProps extends TextInputProps {
         activeLocale?: keyof NCoreUIKit.LocaleKey;
     };
     spreadBehaviour?: TextAreaInputSpreadBehaviour;
+    hintTextContainerStyle?: StyleProp<ViewStyle>;
+    contentContainerStyle?: StyleProp<ViewStyle>;
     onChangeText?: (value: string) => void;
     validation?: (text: string) => boolean;
     isAutoKeyboardDismissOnBlur?: boolean;
-    hintTextContainerStyle?: ViewStyle;
-    contentContainerStyle?: ViewStyle;
+    cleanIconStyle?: StyleProp<ViewStyle>;
+    rightIconStyle?: StyleProp<ViewStyle>;
     inputStyle?: StyleProp<TextStyle>;
+    iconStyle?: StyleProp<ViewStyle>;
     isShowLengthLimiter?: boolean;
     rightIconOnPress?: () => void;
     hintTextIcon?: NCoreUIKitIcon;
     isShowHintTextIcon?: boolean;
     isUpdateOnRealtime?: boolean;
-    cleanIconStyle?: ViewStyle;
-    rightIconStyle?: ViewStyle;
+    style?: StyleProp<ViewStyle>;
     rightIcon?: NCoreUIKitIcon;
     isShowSubTitle?: boolean;
     isCleanEnabled?: boolean;
     iconOnPress?: () => void;
     type?: TextAreaInputType;
     initialValue?: string;
-    iconStyle?: ViewStyle;
     icon?: NCoreUIKitIcon;
     placeholder?: string;
     isRequired?: boolean;
@@ -97,7 +98,6 @@ interface ITextAreaInputProps extends TextInputProps {
     maxHeight?: number;
     hintText?: string;
     subTitle?: string;
-    style?: ViewStyle;
     title?: string;
     id?: string;
 };

+ 7 - 3
src/components/textInput/index.tsx

@@ -201,9 +201,13 @@ const TextInput: RefForwardingComponent<ITextInputRef, ITextInputProps> = ({
     const updateValue = (text: string) => {
         setValue(text);
 
-        inputRef.current?.setNativeProps({
-            text: text
-        });
+        if (Platform.OS !== "web") {
+            inputRef.current?.setNativeProps({
+                text: text
+            });
+        } else if (inputRef.current) {
+            (inputRef.current as unknown as { value: string }).value = text;
+        }
     };
 
     const onFocus = () => {

+ 2 - 0
src/index.tsx

@@ -21,6 +21,7 @@ export {
     PaletteSwitcher,
     DateTimePicker,
     LocaleSwitcher,
+    MarkdownEditor,
     MarkdownViewer,
     DateTimeSheet,
     MonthSelector,
@@ -94,6 +95,7 @@ export type {
     IDateTimePickerProps,
     ILocaleSwitcherProps,
     IMarkdownViewerProps,
+    IMarkdownEditorProps,
     AvatarGroupSizeType,
     ChipSpreadBehaviour,
     DateTimePickerTypes,

+ 37 - 1
src/variants/locales/default.json

@@ -20,22 +20,40 @@
             "copy-success-to-clipboard": "✅ Başarıyla kopyalandı.",
             "copy-error-to-clipboard": "❌ Kopyalama başarısız.",
             "selected-options-with-count": "{{0}} seçim yapıldı",
+            "typography-headlineMediumSize": "H-M",
+            "typography-headlineLargeSize": "H-L",
+            "typography-headlineSmallSize": "H-S",
+            "typography-displayMediumSize": "D-M",
             "select-any-date": "Bir tarih seçin",
+            "typography-displayLargeSize": "D-L",
+            "typography-displaySmallSize": "D-S",
             "clean-selection": "Seçimi Temizle",
+            "typography-labelMediumSize": "L-M",
+            "typography-labelLargeSize": "L-L",
+            "typography-labelSmallSize": "L-S",
+            "typography-titleMediumSize": "H2",
             "select-an-option": "Seçim yapın",
+            "typography-bodyMediumSize": "H5",
+            "typography-titleLargeSize": "H1",
+            "typography-titleSmallSize": "H3",
             "start-time": "Başlangıç Zamanı",
+            "typography-bodyLargeSize": "H4",
+            "typography-bodySmallSize": "H6",
             "clean-all": "Tümünü Temizle",
             "is-optional": "Opsiyonel",
             "select-all": "Tümünü Seç",
             "components": "Bileşenler",
             "end-time": "Bitiş Zamanı",
             "go-to-today": "Bugün",
+            "preview": "Önizleme",
             "home": "Ana Sayfa",
             "monthly": "Aylık",
             "yearly": "Yıllık",
             "cancel": "İptal",
             "daily": "Günlük",
+            "live": "Canlı",
             "search": "Ara",
+            "write": "Yaz",
             "menu": "Menü",
             "ok": "Tamam",
             "or": "ya da"
@@ -132,8 +150,23 @@
             "selected-options-with-count": "{{0}} items selected",
             "copy-error-to-clipboard": "❌ Copy unsuccessful.",
             "select-an-option": "Select an option",
+            "typography-headlineMediumSize": "H-M",
+            "typography-headlineLargeSize": "H-L",
+            "typography-headlineSmallSize": "H-S",
+            "typography-displayMediumSize": "D-M",
             "clean-selection": "Clean Selection",
             "select-any-date": "Select any date",
+            "typography-displayLargeSize": "D-L",
+            "typography-displaySmallSize": "D-S",
+            "typography-labelMediumSize": "L-M",
+            "typography-labelLargeSize": "L-L",
+            "typography-labelSmallSize": "L-S",
+            "typography-titleMediumSize": "H2",
+            "typography-bodyMediumSize": "H5",
+            "typography-titleLargeSize": "H1",
+            "typography-titleSmallSize": "H3",
+            "typography-bodyLargeSize": "H4",
+            "typography-bodySmallSize": "H6",
             "start-time": "Start Time",
             "select-all": "Select All",
             "components": "Components",
@@ -142,12 +175,15 @@
             "clean-all": "Clean All",
             "end-time": "End Time",
             "monthly": "Monthly",
-            "cancel": "Cancel",
+            "preview": "Preview",
             "search": "Search",
+            "cancel": "Cancel",
             "yearly": "Yearly",
             "daily": "Daily",
+            "write": "Write",
             "home": "Home",
             "menu": "Menu",
+            "live": "Live",
             "ok": "Okey",
             "or": "or"
         },

+ 1 - 0
src/variants/themes/default.json

@@ -64,6 +64,7 @@
             "scrollBarTrack": 6,
             "scrollBarThumb": 4,
             "seperator": 0.5,
+            "indicator": 4,
             "subtract": 3,
             "line": 2
         },