Przeglądaj źródła

Feature: eslint features and CRLF updated.

lfabl 2 tygodni temu
rodzic
commit
0a5f90128d

+ 4 - 0
.agents/AGENTS.md

@@ -38,3 +38,7 @@ Always follow these rules by default. Pay "extreme attention" (aşırı dikkat)
 - **Incorrect:** `flex1`, `fullWidth`, `transparentBackground`
 - **Correct:** `container`, `content`, `titleContainer`, `title`, `emptyCheckIcon`
 - For example: The outermost `<View>` in a component is usually `container`. A wrapper for content is `contentContainer`. A view wrapping a title is `titleContainer`. Always look at the semantic purpose of the element in the UI hierarchy.
+
+## Line Endings (CRLF)
+- Always use Windows-style CRLF line endings (`\r\n`) for all text and code files, unless a specific file or environment strictly requires LF (`\n`).
+

+ 7 - 0
.editorconfig

@@ -0,0 +1,7 @@
+root = true
+
+[*]
+trim_trailing_whitespace = true
+insert_final_newline = true
+end_of_line = crlf
+charset = utf-8

+ 6 - 3
.vscode/settings.json

@@ -27,14 +27,17 @@
             "pattern": "./src"
         }
     ],
-    "editor.tabSize": 4,
-    "editor.insertSpaces": true,
-    "files.eol": "\n",
+    "editor.unicodeHighlight.invisibleCharacters": false,
+    "editor.unicodeHighlight.ambiguousCharacters": false,
+    "editor.unicodeHighlight.nonBasicASCII": false,
     "files.trimTrailingWhitespace": true,
     "files.insertFinalNewline": true,
     "files.trimFinalNewlines": true,
     "eslint.lintTask.enable": true,
     "eslint.format.enable": true,
+    "editor.insertSpaces": true,
+    "editor.tabSize": 4,
+    "files.eol": "\r\n",
     "[typescript]": {
         "editor.defaultFormatter": "dbaeumer.vscode-eslint"
     },

+ 707 - 23
eslint-local-rules/index.js

@@ -12,18 +12,36 @@ module.exports = {
                         s => s.type === "ImportSpecifier"
                     );
 
-                    if (specifiers.length <= 1) return;
+                    if (specifiers.length === 0) return;
 
                     const sourceCode = context.getSourceCode();
+                    const openBrace = sourceCode.getFirstToken(node, t => t.value === "{");
+                    const closeBrace = sourceCode.getLastToken(node, t => t.value === "}");
+
+                    if (!openBrace || !closeBrace) return;
+
+                    const firstSpecifier = specifiers[0];
+                    if (firstSpecifier.loc.start.line - openBrace.loc.end.line !== 1) {
+                        context.report({
+                            node: firstSpecifier,
+                            message: "There should be exactly one newline after '{'.",
+                            fix(fixer) {
+                                return fixer.replaceTextRange([
+                                    openBrace.range[1],
+                                    firstSpecifier.range[0]
+                                ], "\n    ");
+                            }
+                        });
+                    }
 
                     for (let i = 0; i < specifiers.length - 1; i++) {
                         const current = specifiers[i];
                         const next = specifiers[i + 1];
 
-                        if (current.loc.end.line === next.loc.start.line) {
+                        if (next.loc.start.line - current.loc.end.line !== 1) {
                             context.report({
                                 node: next,
-                                message: "Each import specifier should be on a new line",
+                                message: "Each import specifier should be on a new line without blank lines between them.",
                                 fix(fixer) {
                                     const comma = sourceCode.getTokenBefore(next);
                                     return fixer.replaceTextRange(
@@ -50,6 +68,20 @@ module.exports = {
                             }
                         });
                     }
+
+                    const tokenBeforeClose = sourceCode.getTokenBefore(closeBrace);
+                    if (closeBrace.loc.start.line - tokenBeforeClose.loc.end.line !== 1) {
+                        context.report({
+                            node: closeBrace,
+                            message: "There should be exactly one newline before '}'.",
+                            fix(fixer) {
+                                return fixer.replaceTextRange([
+                                    tokenBeforeClose.range[1],
+                                    closeBrace.range[0]
+                                ], "\n");
+                            }
+                        });
+                    }
                 }
             };
         }
@@ -311,7 +343,7 @@ module.exports = {
             const sourceCode = context.getSourceCode();
 
             return {
-                ArrayExpression(node) {
+                "ArrayExpression, ArrayPattern"(node) {
                     if (node.elements.length === 0) return;
 
                     const openBracket = sourceCode.getFirstToken(node);
@@ -429,24 +461,26 @@ module.exports = {
 
                     const categorized = categorizeImports(imports, sourceCode);
 
-                    for (let i = 0; i < imports.length - 1; i++) {
-                        const current = imports[i];
-                        const next = imports[i + 1];
-
-                        const currentCategory = getImportCategory(current, sourceCode);
-                        const nextCategory = getImportCategory(next, sourceCode);
+                    let isSorted = true;
+                    let errorNode = null;
 
-                        if (currentCategory > nextCategory) {
-                            context.report({
-                                node: next,
-                                message: `Import from "${next.source.value}" should come before "${current.source.value}"`,
-                                fix(fixer) {
-                                    return fixImportOrder(fixer, imports, categorized, sourceCode);
-                                }
-                            });
+                    for (let i = 0; i < imports.length; i++) {
+                        if (imports[i] !== categorized[i].node) {
+                            isSorted = false;
+                            errorNode = imports[i];
                             break;
                         }
                     }
+
+                    if (!isSorted) {
+                        context.report({
+                            node: errorNode,
+                            message: "Imports are not sorted correctly according to custom rules.",
+                            fix(fixer) {
+                                return fixImportOrder(fixer, imports, categorized, sourceCode);
+                            }
+                        });
+                    }
                 }
             };
 
@@ -492,15 +526,31 @@ module.exports = {
             }
 
             function categorizeImports(imports, sourceCode) {
-                return imports.map(imp => ({
-                    node: imp,
-                    category: getImportCategory(imp, sourceCode),
-                    source: imp.source.value
-                })).sort((a, b) => {
+                return imports.map(imp => {
+                    const isDefaultImport = imp.specifiers.some(s => s.type === "ImportDefaultSpecifier");
+                    const textLength = sourceCode.getText(imp).replace(/\s+/g, "").length;
+                    return {
+                        node: imp,
+                        category: getImportCategory(imp, sourceCode),
+                        source: imp.source.value,
+                        isDefaultImport,
+                        textLength
+                    };
+                }).sort((a, b) => {
                     if (a.category !== b.category) {
                         return a.category - b.category;
                     }
 
+                    if (a.category >= 6) {
+                        if (a.isDefaultImport !== b.isDefaultImport) {
+                            return a.isDefaultImport ? 1 : -1;
+                        }
+
+                        if (a.textLength !== b.textLength) {
+                            return b.textLength - a.textLength;
+                        }
+                    }
+
                     return a.source.localeCompare(b.source);
                 });
             }
@@ -993,5 +1043,639 @@ module.exports = {
                 }
             };
         }
+    },
+    "sort-jsx-props": {
+        meta: {
+            type: "layout",
+            fixable: "code",
+            schema: []
+        },
+        create(context) {
+            return {
+                JSXOpeningElement(node) {
+                    const attributes = node.attributes;
+                    if (!attributes || attributes.length <= 1) return;
+
+                    const sourceCode = context.getSourceCode();
+
+                    const getAttrLength = (attr) => {
+                        const text = sourceCode.getText(attr);
+                        return text.replace(/\s+/g, "").length;
+                    };
+
+                    const groups = [];
+                    let currentGroup = [];
+                    for (const attr of attributes) {
+                        if (attr.type === "JSXSpreadAttribute") {
+                            if (currentGroup.length > 1) {
+                                groups.push(currentGroup);
+                            }
+                            currentGroup = [];
+                        } else {
+                            currentGroup.push(attr);
+                        }
+                    }
+                    if (currentGroup.length > 1) {
+                        groups.push(currentGroup);
+                    }
+
+                    for (const group of groups) {
+                        const sortedGroup = [...group].sort((a, b) => {
+                            return getAttrLength(b) - getAttrLength(a);
+                        });
+
+                        let isSorted = true;
+                        for (let i = 0; i < group.length; i++) {
+                            if (group[i] !== sortedGroup[i]) {
+                                isSorted = false;
+                                break;
+                            }
+                        }
+
+                        if (!isSorted) {
+                            context.report({
+                                node: group[0],
+                                message: "JSX attributes should be sorted from longest to shortest.",
+                                fix(fixer) {
+                                    const firstAttr = group[0];
+                                    const startLoc = firstAttr.loc.start;
+                                    const indent = " ".repeat(startLoc.column);
+
+                                    const newText = sortedGroup.map(attr => sourceCode.getText(attr)).join("\n" + indent);
+
+                                    const start = group[0].range[0];
+                                    const end = group[group.length - 1].range[1];
+
+                                    return fixer.replaceTextRange([
+                                        start,
+                                        end
+                                    ], newText);
+                                }
+                            });
+                        }
+                    }
+                }
+            };
+        }
+    },
+    "sort-import-specifiers": {
+        meta: {
+            type: "layout",
+            fixable: "code",
+            schema: []
+        },
+        create(context) {
+            return {
+                ImportDeclaration(node) {
+                    checkSpecifiers(node, "ImportSpecifier", "Import");
+                },
+                ExportNamedDeclaration(node) {
+                    checkSpecifiers(node, "ExportSpecifier", "Export");
+                }
+            };
+
+            function checkSpecifiers(node, specifierType, typeName) {
+                if (!node.specifiers) return;
+                const specifiers = node.specifiers.filter(
+                    s => s.type === specifierType
+                );
+
+                if (specifiers.length <= 1) return;
+
+                const sourceCode = context.getSourceCode();
+
+                const getSpecLength = (spec) => {
+                    return sourceCode.getText(spec).replace(/\s+/g, "").length;
+                };
+
+                const sortedSpecifiers = [...specifiers].sort((a, b) => {
+                    return getSpecLength(b) - getSpecLength(a);
+                });
+
+                let isSorted = true;
+                for (let i = 0; i < specifiers.length; i++) {
+                    if (specifiers[i] !== sortedSpecifiers[i]) {
+                        isSorted = false;
+                        break;
+                    }
+                }
+
+                if (!isSorted) {
+                    context.report({
+                        node,
+                        message: `${typeName} specifiers should be sorted from longest to shortest.`,
+                        fix(fixer) {
+                            const firstSpec = specifiers[0];
+                            const lastSpec = specifiers[specifiers.length - 1];
+
+                            const startLoc = firstSpec.loc.start;
+
+                            let indent = "    ";
+                            if (firstSpec.loc.start.line !== node.loc.start.line) {
+                                indent = " ".repeat(startLoc.column);
+                            }
+
+                            const newText = sortedSpecifiers.map(spec => sourceCode.getText(spec)).join(",\n" + indent);
+
+                            return fixer.replaceTextRange([
+                                firstSpec.range[0],
+                                lastSpec.range[1]
+                            ], newText);
+                        }
+                    });
+                }
+            }
+        }
+    },
+    "sort-translations": {
+        meta: {
+            type: "layout",
+            fixable: "code",
+            schema: []
+        },
+        create(context) {
+            const sourceCode = context.getSourceCode();
+
+            function checkObject(node) {
+                if (!node.properties || node.properties.length <= 1) return;
+
+                let hasIsRTL = false;
+                let hasLocale = false;
+                let translationsProp = null;
+
+                for (const prop of node.properties) {
+                    const keyNode = prop.key;
+                    if (!keyNode) continue;
+
+                    let keyName = "";
+                    if (keyNode.type === "Identifier") keyName = keyNode.name;
+                    else if (keyNode.type === "Literal" || keyNode.type === "JSONLiteral") keyName = keyNode.value;
+
+                    if (keyName === "isRTL") hasIsRTL = true;
+                    if (keyName === "locale") hasLocale = true;
+                    if (keyName === "translations") translationsProp = prop;
+                }
+
+                if (hasIsRTL && hasLocale && translationsProp) {
+                    const translationsObj = translationsProp.value;
+                    if (!translationsObj || !translationsObj.properties || translationsObj.properties.length <= 1) return;
+
+                    const props = translationsObj.properties;
+
+                    const sortedProps = [...props].sort((a, b) => {
+                        const lenA = sourceCode.getText(a.key).length + sourceCode.getText(a.value).length;
+                        const lenB = sourceCode.getText(b.key).length + sourceCode.getText(b.value).length;
+
+                        if (lenA !== lenB) {
+                            return lenB - lenA;
+                        }
+
+                        const keyA = a.key.value || a.key.name || "";
+                        const keyB = b.key.value || b.key.name || "";
+                        return keyA.localeCompare(keyB);
+                    });
+
+                    let isSorted = true;
+                    let errorNode = null;
+                    for (let i = 0; i < props.length; i++) {
+                        if (props[i] !== sortedProps[i]) {
+                            isSorted = false;
+                            errorNode = props[i];
+                            break;
+                        }
+                    }
+
+                    if (!isSorted) {
+                        context.report({
+                            node: errorNode,
+                            message: "Translations keys must be sorted by length (longest to shortest).",
+                            fix(fixer) {
+                                const firstProp = props[0];
+                                const lastProp = props[props.length - 1];
+
+                                const startLoc = firstProp.loc.start;
+                                const indent = " ".repeat(startLoc.column);
+
+                                let newText = "";
+                                sortedProps.forEach((p, index) => {
+                                    newText += sourceCode.getText(p);
+                                    if (index < sortedProps.length - 1) {
+                                        newText += ",\n" + indent;
+                                    }
+                                });
+
+                                return fixer.replaceTextRange([
+                                    firstProp.range[0],
+                                    lastProp.range[1]
+                                ], newText);
+                            }
+                        });
+                    }
+                }
+            }
+
+            return {
+                ObjectExpression: checkObject,
+                JSONObjectExpression: checkObject
+            };
+        }
+    },
+    "ncore-sort-styles": {
+        meta: {
+            type: "layout",
+            fixable: "code",
+            schema: []
+        },
+        create(context) {
+            const sourceCode = context.getSourceCode();
+
+            const shorthandDependencies = {
+                borderRadius: [
+                    "borderTopLeftRadius",
+                    "borderTopRightRadius",
+                    "borderBottomLeftRadius",
+                    "borderBottomRightRadius",
+                    "borderTopStartRadius",
+                    "borderTopEndRadius",
+                    "borderBottomStartRadius",
+                    "borderBottomEndRadius"
+                ],
+                padding: [
+                    "paddingHorizontal",
+                    "paddingVertical",
+                    "paddingTop",
+                    "paddingRight",
+                    "paddingBottom",
+                    "paddingLeft",
+                    "paddingStart",
+                    "paddingEnd"
+                ],
+                margin: [
+                    "marginHorizontal",
+                    "marginVertical",
+                    "marginTop",
+                    "marginRight",
+                    "marginBottom",
+                    "marginLeft",
+                    "marginStart",
+                    "marginEnd"
+                ],
+                borderWidth: [
+                    "borderTopWidth",
+                    "borderRightWidth",
+                    "borderBottomWidth",
+                    "borderLeftWidth",
+                    "borderStartWidth",
+                    "borderEndWidth"
+                ],
+                borderColor: [
+                    "borderTopColor",
+                    "borderRightColor",
+                    "borderBottomColor",
+                    "borderLeftColor",
+                    "borderStartColor",
+                    "borderEndColor"
+                ],
+                borderStyle: [
+                    "borderTopStyle",
+                    "borderRightStyle",
+                    "borderBottomStyle",
+                    "borderLeftStyle"
+                ],
+                paddingHorizontal: [
+                    "paddingLeft",
+                    "paddingRight",
+                    "paddingStart",
+                    "paddingEnd"
+                ],
+                marginHorizontal: [
+                    "marginLeft",
+                    "marginRight",
+                    "marginStart",
+                    "marginEnd"
+                ],
+                flex: [
+                    "flexGrow",
+                    "flexShrink",
+                    "flexBasis"
+                ],
+                paddingVertical: [
+                    "paddingTop",
+                    "paddingBottom"
+                ],
+                marginVertical: [
+                    "marginTop",
+                    "marginBottom"
+                ]
+            };
+
+            function isStyleObject(node) {
+                if (node.properties.length <= 1) return false;
+
+                if (node.parent.type === "CallExpression" &&
+                    node.parent.callee.type === "MemberExpression" &&
+                    node.parent.callee.object.name === "StyleSheet" &&
+                    node.parent.callee.property.name === "create") {
+                    return true;
+                }
+
+                if (node.parent.type === "Property" &&
+                    node.parent.parent.type === "ObjectExpression" &&
+                    node.parent.parent.parent.type === "CallExpression" &&
+                    node.parent.parent.parent.callee.type === "MemberExpression" &&
+                    node.parent.parent.parent.callee.object.name === "StyleSheet") {
+                    return true;
+                }
+
+                const knownStyleKeys = new Set([
+                    "alignItems",
+                    "justifyContent",
+                    "flexDirection",
+                    "flexWrap",
+                    "flex",
+                    "padding",
+                    "margin",
+                    "paddingTop",
+                    "marginTop",
+                    "paddingBottom",
+                    "marginBottom",
+                    "paddingLeft",
+                    "marginLeft",
+                    "paddingRight",
+                    "marginRight",
+                    "paddingHorizontal",
+                    "marginHorizontal",
+                    "paddingVertical",
+                    "marginVertical",
+                    "backgroundColor",
+                    "color",
+                    "fontSize",
+                    "fontWeight",
+                    "lineHeight",
+                    "textAlign",
+                    "borderWidth",
+                    "borderRadius",
+                    "borderColor",
+                    "borderStyle",
+                    "position",
+                    "top",
+                    "bottom",
+                    "left",
+                    "right",
+                    "display",
+                    "width",
+                    "height",
+                    "minWidth",
+                    "minHeight",
+                    "maxWidth",
+                    "maxHeight",
+                    "alignSelf"
+                ]);
+
+                let hasStyleKey = false;
+                for (const prop of node.properties) {
+                    if (prop.type === "Property" && prop.key.type === "Identifier") {
+                        if (knownStyleKeys.has(prop.key.name)) {
+                            hasStyleKey = true;
+                            break;
+                        }
+                    }
+                }
+                if (hasStyleKey) return true;
+
+                if (node.parent.type === "VariableDeclarator" &&
+                    (node.parent.id.name === "styles" || node.parent.id.name === "stylesheet" || node.parent.id.name === "style")) {
+                    return true;
+                }
+
+                if (node.parent.type === "TSAsExpression" || node.parent.type === "TypeAssertion") {
+                    const typeName = node.parent.typeAnnotation?.typeName?.name;
+                    if (typeName && (typeName === "ViewStyle" || typeName === "TextStyle" || typeName === "ImageStyle")) {
+                        return true;
+                    }
+                }
+
+                if (node.parent.type === "Property" && node.parent.parent.type === "ObjectExpression") {
+                    if (node.parent.parent.parent.type === "VariableDeclarator" &&
+                        (node.parent.parent.parent.id.name === "styles" || node.parent.parent.parent.id.name === "stylesheet")) {
+                        return true;
+                    }
+                }
+
+                return false;
+            }
+
+            function getSortedProperties(properties) {
+                const webStyles = [];
+                const others = [];
+                for (const p of properties) {
+                    if (p.type === "SpreadElement" && p.argument.type === "CallExpression" && p.argument.callee.name === "webStyle") {
+                        webStyles.push(p);
+                    } else {
+                        others.push(p);
+                    }
+                }
+
+                const chunks = [];
+                let currentChunk = [];
+                const chunkSeparators = [];
+
+                for (const p of others) {
+                    if (p.type === "SpreadElement") {
+                        chunks.push(currentChunk);
+                        chunkSeparators.push(p);
+                        currentChunk = [];
+                    } else {
+                        currentChunk.push(p);
+                    }
+                }
+                chunks.push(currentChunk);
+
+                function sortChunk(chunk) {
+                    if (chunk.length <= 1) return chunk;
+
+                    const getLen = (node) => sourceCode.getText(node).length;
+
+                    const initialSorted = chunk.map((node, i) => ({
+                        node,
+                        originalIndex: i,
+                        length: getLen(node)
+                    })).sort((a, b) => {
+                        if (a.length !== b.length) return b.length - a.length;
+                        return a.originalIndex - b.originalIndex;
+                    });
+
+                    const propNames = chunk.map(p => {
+                        if (p.type === "Property") {
+                            if (p.key.type === "Identifier") return p.key.name;
+                            if (p.key.type === "Literal") return p.key.value;
+                        }
+                        return null;
+                    });
+
+                    const prerequisites = new Map();
+                    for (let i = 0; i < chunk.length; i++) {
+                        prerequisites.set(i, new Set());
+                    }
+                    for (let i = 0; i < propNames.length; i++) {
+                        const nameA = propNames[i];
+                        if (!nameA) continue;
+                        const deps = shorthandDependencies[nameA];
+                        if (deps) {
+                            for (let j = 0; j < propNames.length; j++) {
+                                if (i === j) continue;
+                                if (deps.includes(propNames[j])) {
+                                    prerequisites.get(j).add(i);
+                                }
+                            }
+                        }
+                    }
+
+                    const result = [];
+                    const placedIndices = new Set();
+                    const pool = [...initialSorted];
+
+                    while (pool.length > 0) {
+                        let placedAny = false;
+                        for (let i = 0; i < pool.length; i++) {
+                            const item = pool[i];
+                            const itemOriginalIndex = item.originalIndex;
+                            const reqs = prerequisites.get(itemOriginalIndex);
+
+                            let canPlace = true;
+                            for (const req of reqs) {
+                                if (!placedIndices.has(req)) {
+                                    canPlace = false;
+                                    break;
+                                }
+                            }
+
+                            if (canPlace) {
+                                result.push(item.node);
+                                placedIndices.add(itemOriginalIndex);
+                                pool.splice(i, 1);
+                                placedAny = true;
+                                break;
+                            }
+                        }
+                        if (!placedAny) {
+                            const item = pool.shift();
+                            result.push(item.node);
+                            placedIndices.add(item.originalIndex);
+                        }
+                    }
+                    return result;
+                }
+
+                const finalResult = [];
+                for (let i = 0; i < chunks.length; i++) {
+                    finalResult.push(...sortChunk(chunks[i]));
+                    if (i < chunkSeparators.length) {
+                        finalResult.push(chunkSeparators[i]);
+                    }
+                }
+                finalResult.push(...webStyles);
+                return finalResult;
+            }
+
+            return {
+                ObjectExpression(node) {
+                    if (!isStyleObject(node)) return;
+
+                    const oldOrder = node.properties;
+                    if (oldOrder.length <= 1) return;
+
+                    const newOrder = getSortedProperties(oldOrder);
+
+                    let changed = false;
+                    for (let i = 0; i < oldOrder.length; i++) {
+                        if (oldOrder[i] !== newOrder[i]) {
+                            changed = true;
+                            break;
+                        }
+                    }
+
+                    if (changed) {
+                        context.report({
+                            node: node,
+                            message: "Style properties should be sorted by length (longest to shortest), with shorthand properties appropriately ordered and spread elements at the bottom.",
+                            fix(fixer) {
+                                const fixes = [];
+                                for (let i = 0; i < oldOrder.length; i++) {
+                                    fixes.push(fixer.replaceText(oldOrder[i], sourceCode.getText(newOrder[i])));
+                                }
+                                return fixes;
+                            }
+                        });
+                    }
+                }
+            };
+        }    },
+    "ncore-sort-types": {
+        meta: {
+            type: "layout",
+            fixable: "code",
+            schema: []
+        },
+        create(context) {
+            const sourceCode = context.getSourceCode();
+            
+            function sortMembers(node, members) {
+                if (!members || members.length <= 1) return;
+                
+                const getLen = (n) => {
+                    const text = sourceCode.getText(n);
+                    const lines = text.split(/\r?\n/);
+                    let maxLen = 0;
+                    for (const line of lines) {
+                        const trimmed = line.trim();
+                        if (trimmed.length > maxLen) {
+                            maxLen = trimmed.length;
+                        }
+                    }
+                    return maxLen;
+                };
+                
+                const initialSorted = members.map((member, i) => ({
+                    node: member,
+                    originalIndex: i,
+                    length: getLen(member)
+                })).sort((a, b) => {
+                    if (a.length !== b.length) return b.length - a.length;
+                    return a.originalIndex - b.originalIndex;
+                });
+                
+                let changed = false;
+                for (let i = 0; i < members.length; i++) {
+                    if (members[i] !== initialSorted[i].node) {
+                        changed = true;
+                        break;
+                    }
+                }
+                
+                if (changed) {
+                    context.report({
+                        node: node,
+                        message: "Type properties should be sorted by length (longest to shortest).",
+                        fix(fixer) {
+                            const fixes = [];
+                            for (let i = 0; i < members.length; i++) {
+                                fixes.push(fixer.replaceText(members[i], sourceCode.getText(initialSorted[i].node)));
+                            }
+                            return fixes;
+                        }
+                    });
+                }
+            }
+            
+            return {
+                TSInterfaceBody(node) {
+                    sortMembers(node, node.body);
+                },
+                TSTypeLiteral(node) {
+                    sortMembers(node, node.members);
+                }
+            };
+        }
     }
 };

+ 29 - 4
eslint.config.mjs

@@ -33,12 +33,29 @@ export default tseslint.config(
             "local-rules/no-newline-after-jsx": "error",
             "local-rules/no-static-inline-styles": "error",
             "local-rules/jsx-no-blank-lines": "error",
+            "local-rules/sort-jsx-props": "error",
+            "local-rules/sort-import-specifiers": "error",
+            "local-rules/ncore-sort-styles": "error",
+            "local-rules/ncore-sort-types": "error",
+            "padded-blocks": ["error", "never"],
+            "padding-line-between-statements": [
+                "error",
+                { "blankLine": "never", "prev": "import", "next": "import" }
+            ],
             "@typescript-eslint/no-empty-object-type": [
                 "error",
                 {
                     "allowInterfaces": "with-single-extends"
                 }
             ],
+            "no-multiple-empty-lines": [
+                "error",
+                {
+                    "max": 1,
+                    "maxEOF": 0,
+                    "maxBOF": 0
+                }
+            ],
             "object-property-newline": [
                 "error",
                 {
@@ -195,7 +212,14 @@ export default tseslint.config(
         }
     },
     {
+        plugins: {
+            jsonc,
+            "local-rules": {
+                rules: localRulesPlugin.rules
+            }
+        },
         rules: {
+            "local-rules/sort-translations": "error",
             "jsonc/object-property-newline": [
                 "error",
                 {
@@ -224,6 +248,10 @@ export default tseslint.config(
             "jsonc/indent": [
                 "error",
                 4
+            ],
+            "linebreak-style": [
+                "error",
+                "windows"
             ]
         },
         languageOptions: {
@@ -231,9 +259,6 @@ export default tseslint.config(
         },
         files: [
             "**/*.json"
-        ],
-        plugins: {
-            jsonc
-        }
+        ]
     }
 );

+ 28 - 17
example/src/navigation/index.tsx

@@ -17,8 +17,8 @@ import {
     MainHeader
 } from "ncore-ui-kit";
 import {
-    type LinkingOptions,
     NavigationContainer,
+    type LinkingOptions,
     getStateFromPath,
     useNavigation
 } from "@react-navigation/native";
@@ -39,26 +39,27 @@ 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 MarkdownViewerPage from "../pages/components/markdownViewer";
 import MarkdownEditorPage from "../pages/components/markdownEditor";
+import MarkdownViewerPage from "../pages/components/markdownViewer";
 import ThemeExamplesPage from "../pages/coreFeatures/themeExamples";
-import MonthSelectorPage from "../pages/components/monthSelector";
 import DateTimeSheetPage from "../pages/components/dateTimeSheet";
+import MonthSelectorPage from "../pages/components/monthSelector";
 import PageContainerPage from "../pages/components/pageContainer";
 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 NumericInputPage from "../pages/components/numericInput";
+import TimeSelectorPage from "../pages/components/timeSelector";
 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 BottomSheetPage from "../pages/components/bottomSheet";
 import RadioButtonPage from "../pages/components/radioButton";
 import SelectSheetPage from "../pages/components/selectSheet";
+import ToolsUsagePage from "../pages/coreFeatures/toolsUsage";
+import CodeEditorPage from "../pages/components/codeEditor";
 import CodeViewerPage from "../pages/components/codeViewer";
 import MainHeaderPage from "../pages/components/mainHeader";
 import SelectBoxPage from "../pages/components/selectBox";
@@ -74,8 +75,8 @@ 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 DialogPage from "../pages/components/dialog";
 import HeaderPage from "../pages/components/header";
 import SwitchPage from "../pages/components/switch";
 import ModalPage from "../pages/components/modal";
@@ -92,10 +93,10 @@ const CoreFeaturesStack = createNativeStackNavigator();
 
 const CoreFeaturesNav = () => {
     return <CoreFeaturesStack.Navigator
-        initialRouteName="ToolsUsage"
         screenOptions={{
             headerShown: true
         }}
+        initialRouteName="ToolsUsage"
     >
         <CoreFeaturesStack.Screen
             component={ToolsUsagePage}
@@ -122,10 +123,10 @@ const CoreFeaturesNav = () => {
 
 const GettingStartedNav = () => {
     return <GettingStartedStack.Navigator
-        initialRouteName="Installation"
         screenOptions={{
             headerShown: true
         }}
+        initialRouteName="Installation"
     >
         <GettingStartedStack.Screen
             component={InstallationPage}
@@ -140,10 +141,10 @@ const GettingStartedNav = () => {
 
 const ComponentsNav = () => {
     return <ComponentsStack.Navigator
-        initialRouteName="Text"
         screenOptions={{
             headerShown: true
         }}
+        initialRouteName="Text"
     >
         <ComponentsStack.Screen
             component={AvatarPage}
@@ -169,6 +170,10 @@ const ComponentsNav = () => {
             component={CodeViewerPage}
             name="CodeViewer"
         />
+        <ComponentsStack.Screen
+            component={CodeEditorPage}
+            name="CodeEditor"
+        />
         <ComponentsStack.Screen
             component={ChipPage}
             name="Chip"
@@ -592,6 +597,11 @@ const RootNav = () => {
                             redirectSub: "CodeViewer",
                             title: "CodeViewer"
                         },
+                        {
+                            redirectMain: "Components",
+                            redirectSub: "CodeEditor",
+                            title: "CodeEditor"
+                        },
                         {
                             redirectMain: "Components",
                             redirectSub: "Chip",
@@ -759,7 +769,6 @@ const RootNav = () => {
     };
 
     return <MainHeader
-        isWorkWithSeperator={true}
         siteLogoProps={{
             imageUrl: activeTheme.indexOf("dark") !== -1 ? "http://ncore.nibgat.space/assets/images/darklogo.png" : "http://ncore.nibgat.space/assets/images/ncorelogo.png",
             onPress: () => {
@@ -773,7 +782,6 @@ const RootNav = () => {
             isWorkWithAction: true,
             isWorkWithFlex1: true
         }}
-        isWorkWithSticky={true}
         renderRight={() => {
             return <View
                 style={[
@@ -788,6 +796,8 @@ const RootNav = () => {
                 <ThemeSwitcher/>
             </View>;
         }}
+        isWorkWithSeperator={true}
+        isWorkWithSticky={true}
         glassEffect={5}
     >
         <NCoreUIKitEmbeddedMenu.Render
@@ -795,10 +805,10 @@ const RootNav = () => {
             id="main-menu"
         >
             <RootStack.Navigator
-                initialRouteName="Home"
                 screenOptions={{
                     headerShown: false
                 }}
+                initialRouteName="Home"
             >
                 <RootStack.Screen
                     component={GettingStartedNav}
@@ -823,9 +833,6 @@ const RootNav = () => {
 
 const Navigation = () => {
     return <NavigationContainer
-        documentTitle={{
-            formatter: (options, route) => `${options?.title ?? route?.name ?? "Home"} || NCore - Design System | UI Kit`
-        }}
         linking={{
             prefixes: [
                 "http://localhost:3000",
@@ -861,6 +868,7 @@ const Navigation = () => {
                             CheckBox: "checkbox",
                             Chip: "chip",
                             CodeViewer: "codeviewer",
+                            CodeEditor: "codeeditor",
                             DateSelector: "dateselector",
                             DateTimePicker: "datetimepicker",
                             DateTimeSheet: "datetimesheet",
@@ -906,6 +914,9 @@ const Navigation = () => {
                 return getStateFromPath(path.toLowerCase(), options);
             }
         } as LinkingOptions<RootStackParamList>}
+        documentTitle={{
+            formatter: (options, route) => `${options?.title ?? route?.name ?? "Home"} || NCore - Design System | UI Kit`
+        }}
     >
         <RootNav/>
     </NavigationContainer>;

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

@@ -38,6 +38,7 @@ export type ComponentsStackParamList = {
     RadioButton: undefined;
     SelectSheet: undefined;
     CodeViewer: undefined;
+    CodeEditor: undefined;
     SelectBox: undefined;
     Seperator: undefined;
     StateCard: undefined;

+ 79 - 0
example/src/pages/components/codeEditor/index.tsx

@@ -0,0 +1,79 @@
+import {
+    useLayoutEffect,
+    useState
+} from "react";
+import {
+    View
+} from "react-native";
+import stylesheet from "./stylesheet";
+import {
+    NCoreUIKitLocalize,
+    PageContainer,
+    CodeEditor,
+    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 CodeEditorPage = () => {
+    const {
+        localize
+    } = NCoreUIKitLocalize.useContext();
+
+    const navigation = useNavigation<NativeStackNavigationProp<RootStackParamList>>();
+
+    useLayoutEffect(() => {
+        navigation.setOptions({
+            header: () => <Header
+                title={localize("codeEditor-title") || "Code Editor"}
+                isWrapSafeareaContext={false}
+                navigation={navigation}
+                isGoBackEnable={true}
+            />,
+            headerShown: true
+        });
+    }, []);
+
+    const [
+        code,
+        setCode
+    ] = useState<string>("const hello = \"world\";\n\nconst greet = async (name) => {\n    console.log(hello, name);\n};\n\ngreet(\"NCore\");");
+
+    return <PageContainer
+        scrollViewProps={{
+            contentContainerStyle: stylesheet.scrollContent
+        }}
+        scrollViewStyle={[
+            stylesheet.container
+        ]}
+        isWorkWithHeaderSpace={false}
+        isScrollable={true}
+    >
+        <View
+            style={[
+                stylesheet.contentContainer
+            ]}
+        >
+            <Text
+                style={stylesheet.marginContainer}
+                variant="bodyMediumSize"
+                color="mid"
+            >
+                {localize("codeEditor-desc") || "This is a CodeEditor component from NCore UI Kit."}
+            </Text>
+            <CodeEditor
+                style={stylesheet.editor}
+                onChangeText={setCode}
+                language="javascript"
+                value={code}
+            />
+        </View>
+    </PageContainer>;
+};
+export default CodeEditorPage;

+ 25 - 0
example/src/pages/components/codeEditor/stylesheet.ts

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

+ 26 - 22
example/src/variants/locales/default.json

@@ -5,13 +5,16 @@
             "typesOverride-globalDtsDesc": "TypeScript projelerinde NCore UI Kit kütüphanesini kullanırken, temanızdaki veya dil dosyanızdaki özel anahtarları TypeScript'e tanıtmak için global.d.ts kullanmanız gerekir.",
             "toolsUsage-ncoreThemeDesc": "Tema değerlerine dinamik olarak erişim sağlar, prop drilling gerektirmez. Renkleri ve boşlukları global olarak kullanabilirsiniz.",
             "installation-peerDepsDesc": "Aşağıdaki paketlerin projenizde kurulu olması gereklidir. Geliştirme ortamınıza uygun olan komut ile (yarn/npm) kurabilirsiniz.",
+            "defaultJSONs-desc": "Projenizde NCore UI Kit'i başlatırken kendi temanızı ve dil dosyanızı nasıl tanımlayacağınızı gösteren örnek yapı taşları.",
             "pageContainer-desc": "Sayfa içeriklerini sararak tema boşluklarına, güvenli alanlara ve kaydırma yapılarına uygun hale getiren temel bileşen.",
             "typesOverride-tsconfigDesc": "Projenizin tsconfig.json dosyasında global.d.ts dosyanızı typeRoots veya include içerisine eklemeyi unutmayın.",
             "stateCard-desc": "Boş durum, hata durumu veya başarılı işlem gibi uygulama anlarını kullanıcıya gösteren bilgilendirici kart bileşeni.",
             "toolsUsage-ncoreToastDesc": "Kullanıcıyı bir işlemin sonucu hakkında bilgilendirmek için interaktif toast mesajları göstermeyi sağlar.",
+            "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.",
             "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.",
             "implementation-desc": "Projelerinizde NCore UI Kit'i kurduktan sonra en dış katmandan nasıl uygulayacağınıza dair örnek.",
             "modal-desc": "Mevcut sayfanın üzerine binen ve kullanıcıyı belirli bir göreve odaklayan kalıcı (modal) pencere bileşeni.",
@@ -31,9 +34,9 @@
             "header-desc": "Sayfaların en üstünde yer alan, başlık ve navigasyon eylemlerini barındıran üst alan bileşeni.",
             "loading-desc": "Arka planda devam eden asenkron bir işlemin veya veri yüklemesinin durumunu gösteren bileşen.",
             "chip-desc": "Küçük bilgi parçalarını, etiketleri veya durumları göstermek için kullanılan kompakt bileşen.",
+            "markdownEditor-desc": "Markdown formatındaki zengin metinleri düzenlemek için kullanılan içerik bileşeni.",
             "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.",
@@ -42,6 +45,7 @@
             "monthSelector-desc": "Bir yıl içerisinden belirli bir ayı veya ayları seçmek için kullanılan bileşen.",
             "sticker-desc": "Eğlenceli görseller veya ikonlar eklemek için kullanılan dinamik yapıştırma bileşeni.",
             "toast-desc": "Ekranın üst kısmında anlık olarak belirip kaybolan sistem mesajlarını gösteren bileşen.",
+            "codeEditor-desc": "Yazılım geliştiriciler için syntax highlighting özellikli kod düzenleme bileşeni.",
             "timeSelector-desc": "Belirli bir zamanı (saat, dakika) seçmek için optimize edilmiş arayüz bileşeni.",
             "rowCard-desc": "Bilgileri veya görselleri yatay bir satır düzeni içinde sunan bilgi kartı bileşeni.",
             "selectBox-desc": "Açılır liste içerisinden tekil veya çoklu seçim yapmayı sağlayan seçici bileşeni.",
@@ -61,9 +65,9 @@
             "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",
+            "markdownEditor-title": "Markdown Düzenleyici (Markdown Editor)",
             "bottomSheet-toggleSwipeClose": "Kaydırarak Kapatmayı Aç/Kapat",
             "highlightButton-toggleCustomPadding": "Özel Boşluğu Aç/Kapat",
             "notificationIndicator-toggleShowCount": "Sayıyı Göster/Gizle",
@@ -107,6 +111,7 @@
             "avatarGroup-toggleLoading": "Yükleniyoru Aç/Kapat",
             "bottomSheet-toggleShowHandle": "Tutamacı Aç/Kapat",
             "checkBox-toggleFlip": "Tersine Çevirmeyi Aç/Kapat",
+            "codeEditor-title": "Kod Düzenleyici (Code Editor)",
             "dateTimePicker-changeVariant": "Varyantı Değiştir",
             "highlightButton-changeSpread": "Yayılımı Değiştir",
             "notificationIndicator-changeType": "Türü Değiştir",
@@ -122,6 +127,7 @@
             "textAreaInput-placeholder": "Bir şeyler yazın...",
             "webScrollbar-item": "Kaydırılabilir İçerik Öğesi",
             "components-paletteSwitcher": "Palet Değiştirici",
+            "defaultJSONs-title": "Varsayılan JSON Dosyaları",
             "dialog-enterContentText": "İçerik Metnini Girin",
             "numericInput-changeVariant": "Varyantı Değiştir",
             "rowCard-toggleDisabled": "Devre Dışıyı Aç/Kapat",
@@ -272,6 +278,7 @@
             "webScrollbar-title": "Web Scrollbar",
             "change-spread": "Yayılımı Değiştir",
             "chip-changeSize": "Boyutu Değiştir",
+            "defaultJSONs": "Varsayılan JSONlar",
             "open-bottom-sheet": "Alt Paneli Aç",
             "rowCard-enterTitle": "Başlık Girin",
             "stateCard-exampleValue": "$ 12,000",
@@ -289,6 +296,7 @@
             "themeExamples-primary": "Birincil",
             "themeExamples-success": "Başarılı",
             "toggle-portal": "Portalı Aç/Kapat",
+            "change-language": "Dili Değiştir",
             "change-status": "Durumu Değiştir",
             "chip-changeType": "Türü Değiştir",
             "coreFeatures": "Temel Özellikler",
@@ -296,8 +304,8 @@
             "radioButton-titleLabel": "Başlık",
             "selectBox-placeholder": "Seçiniz",
             "switch-title": "Anahtar (Switch)",
-            "change-language": "Dili Değiştir",
             "avatar-enterText": "Metin Girin",
+            "codeViewer": "Kod Görüntüleyici",
             "dialog-enterText": "Metin Girin",
             "dialog-title": "İletişim Kutusu",
             "installation-title": "Başlarken",
@@ -309,7 +317,6 @@
             "themeExamples-danger": "Tehlike",
             "toolsUsage": "Araç Kullanımları",
             "typesOverride": "Tip Değiştirme",
-            "codeViewer": "Kod Görüntüleyici",
             "bottomSheet-title": "Alt Panel",
             "change-size": "Boyutu Değiştir",
             "menu-openMenu": "Menu OpenMenu",
@@ -349,14 +356,14 @@
             "dialog": "İletişim Kutusu",
             "enter-text": "Metin Girin",
             "check-box": "Onay Kutusu",
-            "menu-title": "Menu Title",
             "enter-code": "Kodu Girin",
+            "menu-title": "Menu Title",
             "installation": "Kurulum",
             "sub-title": "Alt Başlık",
             "avatar-title": "Avatar",
+            "codeViewer-code": "Kod",
             "menu-home": "Menu Home",
             "text": "Text ( Metin )",
-            "codeViewer-code": "Kod",
             "chip-title": "Çip",
             "content": "İçerik",
             "home": "Ana Sayfa",
@@ -369,12 +376,7 @@
             "close": "Kapat",
             "right": "Sağ",
             "chip": "Çip",
-            "left": "Sol",
-            "defaultJSONs-title": "Varsayılan JSON Dosyaları",
-            "defaultJSONs-desc": "Projenizde NCore UI Kit'i başlatırken kendi temanızı ve dil dosyanızı nasıl tanımlayacağınızı gösteren örnek yapı taşları.",
-            "defaultJSONs-themeDesc": "Uygulamanızın renk paletlerini, tipografisini ve boşluk değerlerini belirleyen tema konfigürasyonu örneği.",
-            "defaultJSONs-localeDesc": "Uygulamanızın desteklediği dilleri ve çevirileri barındıran yerelleştirme konfigürasyonu örneği.",
-            "defaultJSONs": "Varsayılan JSONlar"
+            "left": "Sol"
         },
         "locale": "tr-TR",
         "isRTL": false
@@ -385,6 +387,9 @@
             "installation-peerDepsDesc": "The following packages must be installed in your project. You can install them using the appropriate command (yarn/npm) for your development environment.",
             "toolsUsage-ncoreThemeDesc": "Provides access to theme values dynamically without needing prop drilling. You can consume colors, spaces, radiuses globally.",
             "typesOverride-globalDtsDesc": "When using NCore UI Kit in TypeScript projects, you must use global.d.ts to introduce your custom keys to TypeScript.",
+            "defaultJSONs-desc": "Example building blocks showing how to define your own theme and language file when initializing NCore UI Kit in your project.",
+            "defaultJSONs-themeDesc": "Example of a theme configuration that determines your application's color palettes, typography, and spacing values.",
+            "defaultJSONs-localeDesc": "Example of a localization configuration that holds the languages and translations supported by your application.",
             "pageContainer-desc": "A foundational component that wraps page content to comply with theme spacing, safe areas, and scrolling structures.",
             "installation-desc": "Information on how to install NCore UI Kit, basic usage, and how to integrate it into your development environment.",
             "toolsUsage-ncoreToastDesc": "Gives the ability to throw interactive toast messages globally to inform the user about an action result.",
@@ -425,12 +430,13 @@
             "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.",
+            "markdownEditor-desc": "A content component that allows you to edit rich text in Markdown format.",
             "localeExamples-desc": "Switching between locales and dynamic text usage with localize function.",
             "highlightButton-desc": "An eye-catching button component used to highlight important actions.",
             "toolsUsage-codeViewerDesc": "To render code beautifully in your app and copy it to clipboard.",
             "toolsUsage-codeLocaleComment": "Access current localization translations and active language",
+            "codeEditor-desc": "Code editing component with syntax highlighting for software developers.",
             "monthSelector-desc": "A component used to select a specific month or months within a year.",
             "toolsUsage-codeThemeComment": "Access current theme colors, spaces, and active preferences",
             "dateSelector-desc": "An interface component that allows users to select a specific date.",
@@ -549,6 +555,7 @@
             "components-yearSelector": "Year Selector",
             "dateTimePicker-changeType": "Change Type",
             "dateTimePicker-title": "Date Time Picker",
+            "defaultJSONs-title": "Default JSON Files",
             "embeddedMenu-title": "EmbeddedMenu Title",
             "enter-content-text": "Enter Content Text",
             "numericInput-toggleClean": "Toggle Clean",
@@ -623,6 +630,7 @@
             "textInput-enterTitle": "Enter Title",
             "textInput-toggleIcon": "Toggle Icon",
             "webScrollbar-title": "Web Scrollbar",
+            "change-language": "Change Language",
             "checkBox-changeType": "Change Type",
             "checkBox-toggleFlip": "Toggle Flip",
             "chip-changeSpread": "Change Spread",
@@ -639,7 +647,6 @@
             "toggle-closable": "Toggle Closable",
             "toggle-disabled": "Toggle Disabled",
             "ui-kit-library": "UI Kit - Library",
-            "change-language": "Change Language",
             "avatarGroup-title": "Avatar Group",
             "bottomSheet-title": "Bottom Sheet",
             "gettingStarted": "Getting Started",
@@ -664,6 +671,7 @@
             "themeExamples-success": "Success",
             "themeExamples-warning": "Warning",
             "toggle-loading": "Toggle Loading",
+            "codeEditor-title": "Code Editor",
             "components-snackBar": "SnackBar",
             "radioButton-titleLabel": "Title",
             "siteLogo-exampleTitle": "NİBGAT",
@@ -682,6 +690,7 @@
             "themeExamples-danger": "Danger",
             "toggle-portal": "Toggle Portal",
             "coreFeatures": "Core Features",
+            "defaultJSONs": "Default JSONs",
             "selectBox-title": "Select Box",
             "selectBox-titleLabel": "Title",
             "stateCard-title": "State Card",
@@ -716,17 +725,17 @@
             "themeExamples-info": "Info",
             "toggle-flip": "Toggle Flip",
             "toggle-icon": "Toggle Icon",
+            "codeViewer": "Code Viewer",
             "components-modal": "Modal",
             "components-toast": "Toast",
             "rowCard-title": "Row Card",
             "toolsUsage": "Tools Usage",
-            "codeViewer": "Code Viewer",
             "chip-titleLabel": "Title",
+            "enter-code": "Enter Code",
             "enter-text": "Enter Text",
             "loading-title": "Loading",
             "menu-title": "Menu Title",
             "sticker-title": "Sticker",
-            "enter-code": "Enter Code",
             "codeViewer-code": "Code",
             "avatar-title": "Avatar",
             "check-box": "Check Box",
@@ -749,12 +758,7 @@
             "chip": "Chip",
             "left": "Left",
             "next": "Next",
-            "text": "Text",
-            "defaultJSONs-title": "Default JSON Files",
-            "defaultJSONs-desc": "Example building blocks showing how to define your own theme and language file when initializing NCore UI Kit in your project.",
-            "defaultJSONs-themeDesc": "Example of a theme configuration that determines your application's color palettes, typography, and spacing values.",
-            "defaultJSONs-localeDesc": "Example of a localization configuration that holds the languages and translations supported by your application.",
-            "defaultJSONs": "Default JSONs"
+            "text": "Text"
         },
         "locale": "en-US",
         "isRTL": false

+ 406 - 0
src/components/codeEditor/index.tsx

@@ -0,0 +1,406 @@
+import {
+    useCallback,
+    useEffect,
+    useState,
+    useMemo,
+    useRef
+} from "react";
+import {
+    type TextInputKeyPressEvent,
+    Text as NativeText,
+    ScrollView,
+    TextInput,
+    Platform,
+    View
+} from "react-native";
+import type ICodeEditorProps from "./type";
+import stylesheet, {
+    useStyles
+} from "./stylesheet";
+import {
+    NCoreUIKitTheme
+} from "../../core/hooks";
+import type {
+    TokenType
+} from "../codeViewer/type";
+import {
+    splitTokensIntoLines,
+    parseCode
+} from "../codeViewer/parser";
+
+const CodeEditor = ({
+    isLineNumberVisible = true,
+    onChangeText,
+    containerStyle,
+    language,
+    value,
+    style,
+    ...props
+}: ICodeEditorProps) => {
+    const {
+        typography,
+        colors,
+        spaces
+    } = NCoreUIKitTheme.useContext();
+
+    const inputRef = useRef<TextInput>(null);
+
+    const [
+        selection,
+        setSelection
+    ] = useState<{ end: number; start: number }>({
+        end: 0,
+        start: 0
+    });
+
+    const tokens = useMemo(() => parseCode(value, language), [
+        language,
+        value
+    ]);
+
+    const lines = useMemo(() => {
+        return splitTokensIntoLines(tokens);
+    }, [tokens]);
+
+    const {
+        languageText: languageTextDynamicStyle,
+        editorInput: editorInputDynamicStyle,
+        container: containerDynamicStyle,
+        lineRow: lineRowDynamicStyle,
+        gutter: gutterDynamicStyle,
+        text: textDynamicStyle
+    } = useStyles({
+        isLineNumberVisible,
+        hasLanguage: !!language,
+        typography,
+        colors,
+        spaces
+    });
+
+    const getTokenColor = (type: TokenType) => {
+        return colors.code[type as keyof typeof colors.code] || colors.code.default;
+    };
+
+    const handleKeyPress = (e: TextInputKeyPressEvent) => {
+        if (e.nativeEvent.key === "Tab") {
+            e.preventDefault();
+
+            const before = value.substring(0, selection.start);
+            const after = value.substring(selection.end);
+
+            const newText = before + "    " + after;
+            onChangeText?.(newText);
+        }
+    };
+
+    useEffect(() => {
+        if (Platform.OS === "web" && inputRef.current) {
+            const node = inputRef.current as unknown as HTMLTextAreaElement;
+            const handleKeyDown = (e: KeyboardEvent) => {
+                const target = e.target as HTMLTextAreaElement;
+                const start = target.selectionStart;
+                const end = target.selectionEnd;
+
+                const before = value.substring(0, start);
+                const after = value.substring(end);
+
+                const updateText = (newText: string, newCursorPos: number) => {
+                    e.preventDefault();
+                    onChangeText?.(newText);
+                    setTimeout(() => {
+                        if (inputRef.current) {
+                            const currentTarget = inputRef.current as unknown as HTMLTextAreaElement;
+                            currentTarget.selectionStart = newCursorPos;
+                            currentTarget.selectionEnd = newCursorPos;
+                            setSelection({
+                                start: newCursorPos,
+                                end: newCursorPos
+                            });
+                        }
+                    }, 0);
+                };
+
+                if (e.key === "Tab") {
+                    updateText(before + "    " + after, start + 4);
+                    return;
+                }
+
+                const pairs: Record<string, string> = {
+                    "{": "}",
+                    "[": "]",
+                    "(": ")",
+                    "\"": "\"",
+                    "'": "'",
+                    "`": "`"
+                };
+
+                const prevChar = before.charAt(before.length - 1);
+                const nextChar = after.charAt(0);
+
+                if (pairs[e.key]) {
+                    if (start !== end) {
+                        const selectedText = value.substring(start, end);
+                        updateText(before + e.key + selectedText + pairs[e.key] + after, end + 1);
+                        return;
+                    }
+
+                    if ([
+                        "\"",
+                        "'",
+                        "`"
+                    ].includes(e.key) && nextChar === e.key) {
+                        updateText(value, start + 1);
+                        return;
+                    }
+
+                    if ([
+                        "\"",
+                        "'",
+                        "`"
+                    ].includes(e.key) && /[a-zA-Z0-9]/.test(prevChar)) {
+                        return;
+                    }
+
+                    if (nextChar === "" || /[\s\]})]/ .test(nextChar)) {
+                        updateText(before + e.key + pairs[e.key] + after, start + 1);
+                        return;
+                    }
+                }
+
+                if ([
+                    ")",
+                    "}",
+                    "]"
+                ].includes(e.key) && start === end && nextChar === e.key) {
+                    updateText(value, start + 1);
+                    return;
+                }
+
+                if (e.key === "Backspace" && start === end && start > 0) {
+                    if (
+                        (prevChar === "{" && nextChar === "}") ||
+                        (prevChar === "[" && nextChar === "]") ||
+                        (prevChar === "(" && nextChar === ")") ||
+                        (prevChar === "\"" && nextChar === "\"") ||
+                        (prevChar === "'" && nextChar === "'") ||
+                        (prevChar === "`" && nextChar === "`")
+                    ) {
+                        updateText(before.slice(0, -1) + after.slice(1), start - 1);
+                        return;
+                    }
+                }
+
+                if (e.key === "Enter" && start === end) {
+                    const currentLineText = before.split("\n").pop() || "";
+                    const match = currentLineText.match(/^(\s*)/);
+                    const currentIndentation = match?.[1] || "";
+
+                    if (prevChar === "{" && nextChar === "}") {
+                        updateText(
+                            before + "\n" + currentIndentation + "    \n" + currentIndentation + after,
+                            start + 1 + currentIndentation.length + 4
+                        );
+                        return;
+                    } else if (currentIndentation.length > 0) {
+                        updateText(
+                            before + "\n" + currentIndentation + after,
+                            start + 1 + currentIndentation.length
+                        );
+                        return;
+                    }
+                }
+            };
+
+            node.addEventListener?.("keydown", handleKeyDown);
+            return () => {
+                node.removeEventListener?.("keydown", handleKeyDown);
+            };
+        }
+    }, [
+        value,
+        onChangeText
+    ]);
+
+    const renderBackground = useCallback(() => {
+        return <View>
+            {
+                language ?
+                    <NativeText
+                        style={[
+                            stylesheet.content,
+                            textDynamicStyle,
+                            languageTextDynamicStyle
+                        ]}
+                        selectable={false}
+                    >
+                        // {language}
+                    </NativeText>
+                :
+                    null
+            }
+            {lines.map((lineTokens, index) => {
+                return <View
+                    style={[
+                        stylesheet.lineRow,
+                        lineRowDynamicStyle
+                    ]}
+                    key={`line-${index}`}
+                >
+                    {
+                        isLineNumberVisible ?
+                            <View
+                                style={[
+                                    stylesheet.gutter,
+                                    gutterDynamicStyle,
+                                    {
+                                        borderRightColor: colors.content.border.subtle
+                                    }
+                                ]}
+                            >
+                                <NativeText
+                                    style={[
+                                        stylesheet.lineNumber,
+                                        {
+                                            fontFamily: Platform.OS === "web" ? "monospace" : "System",
+                                            color: colors.content.text.low
+                                        }
+                                    ]}
+                                    selectable={false}
+                                >
+                                    {index + 1}
+                                </NativeText>
+                            </View>
+                        :
+                            null
+                    }
+                    <NativeText
+                        style={[
+                            stylesheet.content,
+                            textDynamicStyle,
+                            {
+                                color: colors.content.text.high
+                            }
+                        ]}
+                    >
+                        {lineTokens.map((token, tIndex) => {
+                            return <NativeText
+                                style={{
+                                    color: getTokenColor(token.type)
+                                }}
+                                key={`token-${index}-${tIndex}`}
+                            >
+                                {token.value}
+                            </NativeText>;
+                        })}
+                    </NativeText>
+                </View>;
+            })}
+            {
+                lines.length === 0 ?
+                    <View
+                        style={[
+                            stylesheet.lineRow,
+                            lineRowDynamicStyle
+                        ]}
+                    >
+                        {
+                            isLineNumberVisible ?
+                                <View
+                                    style={[
+                                        stylesheet.gutter,
+                                        gutterDynamicStyle,
+                                        {
+                                            borderRightColor: colors.content.border.subtle
+                                        }
+                                    ]}
+                                >
+                                    <NativeText
+                                        style={[
+                                            stylesheet.lineNumber,
+                                            {
+                                                fontFamily: Platform.OS === "web" ? "monospace" : "System",
+                                                color: colors.content.text.low
+                                            }
+                                        ]}
+                                        selectable={false}
+                                    >
+                                        1
+                                    </NativeText>
+                                </View>
+                            :
+                                null
+                        }
+                        <NativeText
+                            style={[
+                                stylesheet.content,
+                                textDynamicStyle,
+                                {
+                                    color: colors.content.text.high
+                                }
+                            ]}
+                        >
+                        </NativeText>
+                    </View>
+                :
+                    null
+            }
+        </View>;
+    }, [
+        languageTextDynamicStyle,
+        isLineNumberVisible,
+        lineRowDynamicStyle,
+        gutterDynamicStyle,
+        textDynamicStyle,
+        language,
+        colors,
+        lines
+    ]);
+
+    return <View
+        style={[
+            stylesheet.container,
+            containerDynamicStyle,
+            containerStyle
+        ]}
+    >
+        <ScrollView
+            contentContainerStyle={[
+                stylesheet.scrollContent
+            ]}
+            showsHorizontalScrollIndicator={true}
+            style={[
+                stylesheet.scrollView
+            ]}
+            horizontal={true}
+        >
+            <View
+                style={[
+                    stylesheet.scrollContent
+                ]}
+            >
+                {renderBackground()}
+                <TextInput
+                    {...props}
+                    onSelectionChange={(e) => {
+                        setSelection(e.nativeEvent.selection);
+                        props.onSelectionChange?.(e);
+                    }}
+                    style={[
+                        stylesheet.editorInput,
+                        editorInputDynamicStyle,
+                        style
+                    ]}
+                    onChangeText={onChangeText}
+                    onKeyPress={handleKeyPress}
+                    autoCapitalize="none"
+                    autoCorrect={false}
+                    spellCheck={false}
+                    multiline={true}
+                    ref={inputRef}
+                    value={value}
+                />
+            </View>
+        </ScrollView>
+    </View>;
+};
+export default CodeEditor;

+ 136 - 0
src/components/codeEditor/stylesheet.ts

@@ -0,0 +1,136 @@
+import {
+    type TextStyle,
+    type ViewStyle,
+    StyleSheet,
+    Platform
+} from "react-native";
+import type {
+    ICodeEditorDynamicStyleProps
+} from "./type";
+import {
+    webStyle
+} from "../../utils";
+
+const stylesheet = StyleSheet.create({
+    editorInput: {
+        fontFamily: Platform.OS === "web" ? "monospace" : "System",
+        backgroundColor: "transparent",
+        textAlignVertical: "top",
+        position: "absolute",
+        color: "transparent",
+        bottom: 0,
+        right: 0,
+        left: 0,
+        top: 0,
+        ...webStyle({
+            caretColor: "inherit",
+            outline: "none",
+            resize: "none"
+        })
+    },
+    gutter: {
+        justifyContent: "flex-end",
+        alignItems: "flex-start",
+        flexDirection: "row",
+        borderRightWidth: 1,
+        ...webStyle({
+            userSelect: "none"
+        })
+    },
+    content: {
+        fontFamily: Platform.OS === "web" ? "monospace" : "System",
+        ...webStyle({
+            whiteSpace: Platform.OS === "web" ? "pre" : "pre-wrap"
+        })
+    },
+    lineNumber: {
+        textAlign: "right",
+        marginRight: 4,
+        lineHeight: 24,
+        fontSize: 12,
+        ...webStyle({
+            userSelect: "none"
+        })
+    },
+    container: {
+        alignItems: "flex-start",
+        flexDirection: "row",
+        position: "relative",
+        borderRadius: 10,
+        borderWidth: 1
+    },
+    lineRow: {
+        alignItems: "flex-start",
+        flexDirection: "row",
+        minHeight: 24
+    },
+    scrollContent: {
+        minWidth: "100%",
+        flexGrow: 1
+    },
+    scrollView: {
+        flex: 1
+    }
+});
+
+export const useStyles = ({
+    isLineNumberVisible = true,
+    hasLanguage = false,
+    typography,
+    colors,
+    spaces
+}: ICodeEditorDynamicStyleProps) => {
+    let gutterPaddingRight = 6;
+    let gutterMarginRight = 8;
+    let gutterWidth = 32;
+
+    if (!isLineNumberVisible) {
+        gutterPaddingRight = 0;
+        gutterMarginRight = 0;
+        gutterWidth = 0;
+    }
+
+    const languageMarginLeft = spaces.spacingSm;
+
+    const styles = {
+        editorInput: {
+            ...typography.bodyMediumSize,
+            lineHeight: 24,
+            padding: 0,
+            paddingLeft: isLineNumberVisible ? (gutterWidth + gutterMarginRight) : spaces.spacingSm,
+            paddingTop: hasLanguage ? 32 : 0,
+            ...webStyle({
+                caretColor: colors.content.text.high
+            })
+        } as TextStyle,
+        languageText: {
+            color: colors.content.text.low,
+            marginBottom: spaces.spacingSm,
+            marginLeft: languageMarginLeft,
+            ...webStyle({
+                userSelect: "none"
+            })
+        } as TextStyle,
+        container: {
+            backgroundColor: colors.content.container.subtle,
+            borderColor: colors.content.border.subtle,
+            marginVertical: spaces.spacingSm,
+            padding: spaces.spacingMd
+        } as ViewStyle,
+        gutter: {
+            paddingRight: gutterPaddingRight,
+            marginRight: gutterMarginRight,
+            width: gutterWidth
+        } as ViewStyle,
+        lineRow: {
+            marginLeft: !isLineNumberVisible ? spaces.spacingSm : 0
+        } as ViewStyle,
+        text: {
+            ...typography.bodyMediumSize,
+            lineHeight: 24
+        } as TextStyle
+    };
+
+    return styles;
+};
+export default stylesheet;

+ 25 - 0
src/components/codeEditor/type.ts

@@ -0,0 +1,25 @@
+import {
+    type TextInputProps,
+    type ViewStyle
+} from "react-native";
+
+export interface ICodeEditorDynamicStyleProps {
+    typography: NCoreUIKit.ActivePalette["typography"];
+    spaces: NCoreUIKit.ActivePalette["spaces"];
+    colors: NCoreUIKit.ActivePalette["colors"];
+    isLineNumberVisible?: boolean;
+    hasLanguage?: boolean;
+}
+
+interface ICodeEditorProps extends Omit<TextInputProps, "style" | "value" | "onChangeText"> {
+    onChangeText?: (text: string) => void;
+    isLineNumberVisible?: boolean;
+    containerStyle?: ViewStyle;
+    style?: ViewStyle;
+    language?: string;
+    value: string;
+}
+
+export type {
+    ICodeEditorProps as default
+};

+ 7 - 7
src/components/codeViewer/parser.ts

@@ -595,8 +595,8 @@ const getTokenRegex = (language?: string): RegExp => {
         "(?<keyword>" + keywordsRegexStr + ")|" +
         "(?<htmlTag>(?<=<\\/?)[a-z][a-zA-Z0-9-]*\\b)|" +
         "(?<component>\\b[A-Z][a-zA-Z0-9_$]*\\b)|" +
-        "(?<function>\\b[a-zA-Z_$][a-zA-Z0-9_$]*(?=\\s*\\())|" +
-        "(?<property>\\b[a-zA-Z_$][a-zA-Z0-9_$]*(?=\\s*=))|" +
+        "(?<function>\\b[a-zA-Z_$][a-zA-Z0-9_$]*(?=\\s*\\()|\\b[a-zA-Z_$][a-zA-Z0-9_$]*(?=\\s*[:=]\\s*(?:async\\s+)?(?:\\([^)]*\\)|[a-zA-Z_$][a-zA-Z0-9_$]*)\\s*=>)|\\b[a-zA-Z_$][a-zA-Z0-9_$]*(?=\\s*[:=]\\s*function\\b))|" +
+        "(?<property>\\b[a-zA-Z_$][a-zA-Z0-9_$]*(?=\\s*[:=]))|" +
         "(?<variable>\\b[a-zA-Z_$][a-zA-Z0-9_$]*\\b)|" +
         "(?<number>\\b\\d+(?:\\.\\d+)?\\b)|" +
         "(?<arrow>=>)|" +
@@ -675,9 +675,9 @@ export const splitTokensIntoLines = (tokens: Token[]): Token[][] => {
             currentLine.push(token);
         }
     });
-    if (currentLine.length > 0) {
-        lines.push(currentLine);
-    }
+
+    lines.push(currentLine);
+
     return lines;
 };
 
@@ -687,12 +687,12 @@ export const getLineIndentation = (lineTokens: Token[]): number => {
         if (token.type === "text") {
             const match = token.value.match(/^(\s+)/);
 
-            if (match) {
+            if(match) {
                 const matchLen = (match[1] as string).length;
 
                 indentation += matchLen;
 
-                if (matchLen !== token.value.length) {
+                if(matchLen !== token.value.length) {
                     break;
                 }
             } else if (token.value.length > 0) {

+ 8 - 8
src/components/header/index.tsx

@@ -16,12 +16,12 @@ import stylesheet, {
 import {
     NCoreUIKitTheme
 } from "../../core/hooks";
-import {
-    ChevronLeftIcon
-} from "lucide-react-native";
 import {
     SafeAreaView
 } from "react-native-safe-area-context";
+import {
+    ChevronLeftIcon
+} from "lucide-react-native";
 import Button from "../button";
 import Text from "../text";
 
@@ -106,7 +106,7 @@ const Header = <T extends NavigationType>({
             </View>;
         }
 
-        if(!isGoBackEnable || !navigation) {
+        if(!isGoBackEnable || !navigation || !navigation.canGoBack()) {
             if(leftContainerWidth === null) setLeftContainerWidth(0);
             return <View/>;
         }
@@ -122,10 +122,6 @@ const Header = <T extends NavigationType>({
             ]}
         >
             <Button
-                spreadBehaviour="free"
-                variant="ghost"
-                type="neutral"
-                size="small"
                 icon={({
                     color,
                     size
@@ -140,6 +136,10 @@ const Header = <T extends NavigationType>({
                         (navigation as unknown as { goBack: () => void }).goBack();
                     }
                 }}
+                spreadBehaviour="free"
+                variant="ghost"
+                type="neutral"
+                size="small"
             />
         </View>;
     };

+ 4 - 1
src/components/header/type.ts

@@ -18,7 +18,10 @@ export type HeaderDynamicStyleType = {
     headerSpace?: number;
 };
 
-export type NavigationType = unknown;
+export type NavigationType = {
+    canGoBack: () => boolean;
+    goBack: () => void;
+};
 
 export type ActionItem = IButtonProps;
 

+ 8 - 0
src/components/index.ts

@@ -430,3 +430,11 @@ export type {
     MarkdownDynamicStyles,
     MarkdownEditorMode
 } from "./markdownEditor/type";
+
+export {
+    default as CodeEditor
+} from "./codeEditor";
+
+export type {
+    default as ICodeEditorProps
+} from "./codeEditor/type";

+ 4 - 2
src/index.tsx

@@ -38,6 +38,7 @@ export {
     BottomSheet,
     RadioButton,
     SelectSheet,
+    CodeEditor,
     CodeViewer,
     MainHeader,
     MenuButton,
@@ -51,8 +52,8 @@ export {
     Loading,
     RowCard,
     Sticker,
-    Avatar,
     Button,
+    Avatar,
     Dialog,
     Header,
     Switch,
@@ -71,9 +72,9 @@ export type {
     DateTimePickerDynamicStyleType,
     DateTimePickerSpreadBehaviour,
     AvatarTitleAbbreviationTypes,
+    INotificationIndicatorProps,
     AvatarGroupDynamicStyleType,
     AvatarGroupSizeConstantType,
-    INotificationIndicatorProps,
     AvatarStatusIndicatorType,
     CheckBoxDynamicStyleType,
     CheckBoxTypeConstantType,
@@ -120,6 +121,7 @@ export type {
     ISelectSheetProps,
     ITextAreaInputRef,
     TextMarkdownTypes,
+    ICodeEditorProps,
     IEmbeddedMenuRef,
     DateCalendarType,
     IDateSelectorRef,