Procházet zdrojové kódy

Merge branch 'release/1.1.0'

lfabl před 1 týdnem
rodič
revize
40394bcbcf

+ 81 - 2
README.md

@@ -1,6 +1,87 @@
 # NİBGAT® | NCore - Mobile
 The NCore, NİBGAT® UI - Kit. The NİBGAT®'s UI / Component Library.
 
+## 🛠 Tooling & Environment Setup (Optional)
+
+NCore UI Kit provides built-in configuration tools to maintain standard coding guidelines and ensure seamless integration across NİBGAT projects.
+
+### 1. Environment Setup (One-Click)
+
+If you want to use the exact same ESLint rules, TypeScript configs, and VSCode settings used in NCore UI Kit, simply run the setup command in your project root:
+
+```bash
+npx ncore-setup
+```
+*This command automatically copies `.vscode/`, `eslint.config.mjs`, `eslint-local-rules/`, and `tsconfig.json` to your project.*
+
+### 2. Webpack Configuration (React Native Web)
+
+When building a web project with React Native Web, you have two options:
+
+**Option A: Default NCore Webpack Config (Zero-config)**
+Use this if you are starting a new web project and want a ready-to-use configuration.
+
+```javascript
+// webpack.config.js
+const path = require("path");
+const HtmlWebpackPlugin = require("html-webpack-plugin");
+const ESLintPlugin = require("eslint-webpack-plugin");
+const CopyPlugin = require("copy-webpack-plugin");
+const webpack = require("webpack");
+const { createDefaultWebpackConfig } = require("ncore-ui-kit/webpack-config");
+
+module.exports = createDefaultWebpackConfig({
+    appDirectory: path.resolve(__dirname, "./"),
+    HtmlWebpackPlugin,
+    ESLintPlugin,
+    CopyPlugin,
+    webpack
+});
+```
+
+**Option B: Wrap your existing Webpack config**
+If you already have a complex webpack config, you can wrap it to inject necessary polyfills and aliases.
+
+```javascript
+// webpack.config.js
+const { withNCoreUIKitWebpack } = require("ncore-ui-kit/webpack-config");
+const webpack = require("webpack");
+
+let config = {
+    // ... your existing config
+};
+
+module.exports = withNCoreUIKitWebpack(config, webpack);
+```
+
+### 3. Metro Configuration (React Native Mobile)
+
+If you are using this library in a React Native or Expo mobile project, you must update your `metro.config.js` to include the required Node.js core module polyfills (`crypto`, `process`, `stream`).
+
+**For Expo Projects:**
+
+```javascript
+// metro.config.js
+const { getDefaultConfig } = require("expo/metro-config");
+const { withNCoreUIKit } = require("ncore-ui-kit/metro-config");
+
+const config = getDefaultConfig(__dirname);
+module.exports = withNCoreUIKit(config);
+```
+
+**For Bare React Native Projects (CLI):**
+
+```javascript
+// metro.config.js
+const { getDefaultConfig } = require("@react-native/metro-config");
+const { withNCoreUIKit } = require("ncore-ui-kit/metro-config");
+
+const config = getDefaultConfig(__dirname);
+module.exports = withNCoreUIKit(config);
+```
+
+## Usage
+
 ```typescript
 useEffect(() => {
     NCoreUIKitMenu.load({
@@ -98,6 +179,4 @@ useEffect(() => {
         navigation: navigation as unknown as NCoreUIKit.Navigation,
         id: "test"
     });
-}, []);
-
 ```

+ 76 - 0
bin/setup.js

@@ -0,0 +1,76 @@
+#!/usr/bin/env node
+/* eslint-disable @typescript-eslint/no-require-imports */
+
+const fs = require("fs");
+const path = require("path");
+
+const targetDir = process.cwd();
+const sourceDir = path.resolve(__dirname, "..");
+
+console.log("NCore UI Kit Environment Setup Tool");
+console.log("=====================================");
+console.log(`Hedef Proje Dizini: ${targetDir}`);
+console.log("");
+
+// 1. .vscode/settings.json
+const sourceVscodeDir = path.join(sourceDir, ".vscode");
+const targetVscodeDir = path.join(targetDir, ".vscode");
+
+if (fs.existsSync(sourceVscodeDir)) {
+    if (!fs.existsSync(targetVscodeDir)) {
+        fs.mkdirSync(targetVscodeDir, {
+            recursive: true
+        });
+    }
+    const sourceSettings = path.join(sourceVscodeDir, "settings.json");
+    const targetSettings = path.join(targetVscodeDir, "settings.json");
+    if (fs.existsSync(sourceSettings)) {
+        fs.copyFileSync(sourceSettings, targetSettings);
+        console.log("✅ .vscode/settings.json kopyalandı.");
+    }
+}
+
+// 2. eslint.config.mjs
+const sourceEslint = path.join(sourceDir, "eslint.config.mjs");
+const targetEslint = path.join(targetDir, "eslint.config.mjs");
+if (fs.existsSync(sourceEslint)) {
+    fs.copyFileSync(sourceEslint, targetEslint);
+    console.log("✅ eslint.config.mjs kopyalandı.");
+}
+
+// 3. eslint-local-rules
+const sourceLocalRules = path.join(sourceDir, "eslint-local-rules");
+const targetLocalRules = path.join(targetDir, "eslint-local-rules");
+if (fs.existsSync(sourceLocalRules)) {
+    if (!fs.existsSync(targetLocalRules)) {
+        fs.mkdirSync(targetLocalRules, {
+            recursive: true
+        });
+    }
+
+    // Copy all files in the directory
+    const files = fs.readdirSync(sourceLocalRules);
+    files.forEach(file => {
+        const sourceFile = path.join(sourceLocalRules, file);
+        const targetFile = path.join(targetLocalRules, file);
+        if (fs.lstatSync(sourceFile).isFile()) {
+            fs.copyFileSync(sourceFile, targetFile);
+        }
+    });
+    console.log("✅ eslint-local-rules/ kopyalandı.");
+}
+
+// 4. tsconfig.json
+const sourceTsconfig = path.join(sourceDir, "tsconfig.json");
+const targetTsconfig = path.join(targetDir, "tsconfig.json");
+if (fs.existsSync(sourceTsconfig)) {
+    fs.copyFileSync(sourceTsconfig, targetTsconfig);
+    console.log("✅ tsconfig.json kopyalandı.");
+}
+
+console.log("");
+console.log("Kurulum tamamlandı! Lütfen package.json'ınızda aşağıdaki eklentilerin kurulu olduğundan emin olun:");
+console.log("- typescript-eslint");
+console.log("- eslint-plugin-local-rules");
+console.log("- jsonc-eslint-parser vb.");
+console.log("NCore standartlarıyla iyi kodlamalar dileriz!");

+ 5 - 2
eslint.config.mjs

@@ -208,9 +208,12 @@ export default tseslint.config(
     {
         files: [
             "eslint-local-rules/**/*.js",
-            "babel.config.js",
+            "webpack-config.js",
             "**/*.config.mjs",
-            "**/*.config.js"
+            "babel.config.js",
+            "metro-config.js",
+            "**/*.config.js",
+            "bin/setup.js"
         ],
         languageOptions: {
             globals: {

+ 9 - 1
example/mobile/metro.config.js

@@ -18,7 +18,15 @@ config.watchFolders = [
 
 config.resolver.extraNodeModules = {
     "ncore-ui-kit": path.resolve(root, "src"),
-    "node_modules": path.resolve(root, "node_modules")
+    "node_modules": path.resolve(root, "node_modules"),
+    "string_decoder": require.resolve("string_decoder"),
+    "crypto": require.resolve("crypto-browserify"),
+    "stream": require.resolve("stream-browserify"),
+    "vm": require.resolve("vm-browserify"),
+    "process": require.resolve("process"),
+    "buffer": require.resolve("buffer"),
+    "events": require.resolve("events"),
+    "util": require.resolve("util")
 };
 
 config.resolver.nodeModulesPaths = [

+ 171 - 0
example/src/pages/implementation/index.tsx

@@ -81,6 +81,45 @@ const App = () => {
 };
 export default App;`;
 
+    const expoMetroConfigCode = `// For Expo
+const { getDefaultConfig } = require("expo/metro-config");
+const { withNCoreUIKit } = require("ncore-ui-kit/metro-config");
+
+const config = getDefaultConfig(__dirname);
+
+module.exports = withNCoreUIKit(config);`;
+
+    const bareMetroConfigCode = `// For Bare React Native CLI
+const { getDefaultConfig } = require("@react-native/metro-config");
+const { withNCoreUIKit } = require("ncore-ui-kit/metro-config");
+
+const config = getDefaultConfig(__dirname);
+
+module.exports = withNCoreUIKit(config);`;
+
+    const defaultWebpackCode = `// webpack.config.js
+const { createDefaultWebpackConfig } = require("ncore-ui-kit/webpack-config");
+
+module.exports = createDefaultWebpackConfig({
+    appDirectory: require("path").resolve(__dirname, "./"),
+    HtmlWebpackPlugin: require("html-webpack-plugin"),
+    ESLintPlugin: require("eslint-webpack-plugin"),
+    CopyPlugin: require("copy-webpack-plugin"),
+    webpack: require("webpack")
+});`;
+
+    const wrapWebpackCode = `// webpack.config.js
+const { withNCoreUIKitWebpack } = require("ncore-ui-kit/webpack-config");
+const webpack = require("webpack");
+
+let config = {
+    // ... existing configuration
+};
+
+module.exports = withNCoreUIKitWebpack(config, webpack);`;
+
+    const envSetupCode = `npx ncore-setup`;
+
     return <PageContainer
         isWorkWithHeaderSpace={false}
         isScrollable={true}
@@ -123,6 +162,138 @@ export default App;`;
                     language="tsx"
                 />
             </View>
+            <Text
+                style={{
+                    marginBottom: spaces.spacingMd
+                }}
+                variant="bodyMediumSize"
+                color="mid"
+            >
+                Mobil (React Native / Expo) ortamında kurulum yaparken, Node.js core modül polyfill'leri (crypto, process, stream) için metro.config.js dosyanızı NCore UIKit ile sarmalamanız (wrap) gerekir.
+            </Text>
+            <View
+                style={[
+                    stylesheet.borderedCard,
+                    {
+                        backgroundColor: colors.content.container.default,
+                        borderColor: colors.content.border.subtle,
+                        marginBottom: spaces.spacingMd,
+                        padding: spaces.spacingMd
+                    }
+                ]}
+            >
+                <Text
+                    variant="headlineSmallSize"
+                >
+                    {localize("your-project-main-directory")}/metro.config.js (Expo)
+                </Text>
+                <CodeViewer
+                    code={expoMetroConfigCode}
+                    language="javascript"
+                />
+            </View>
+            <View
+                style={[
+                    stylesheet.borderedCard,
+                    {
+                        backgroundColor: colors.content.container.default,
+                        borderColor: colors.content.border.subtle,
+                        marginBottom: spaces.spacingMd,
+                        padding: spaces.spacingMd
+                    }
+                ]}
+            >
+                <Text
+                    variant="headlineSmallSize"
+                >
+                    {localize("your-project-main-directory")}/metro.config.js (React Native CLI)
+                </Text>
+                <CodeViewer
+                    code={bareMetroConfigCode}
+                    language="javascript"
+                />
+            </View>
+            <Text
+                style={{
+                    marginBottom: spaces.spacingMd
+                }}
+                variant="bodyMediumSize"
+                color="mid"
+            >
+                Web (React Native Web) ortamında derleme yapmak için NCore'un hazır Webpack ayarını kullanabilir veya mevcut ayarınızı sarmalayabilirsiniz.
+            </Text>
+            <View
+                style={[
+                    stylesheet.borderedCard,
+                    {
+                        backgroundColor: colors.content.container.default,
+                        borderColor: colors.content.border.subtle,
+                        marginBottom: spaces.spacingMd,
+                        padding: spaces.spacingMd
+                    }
+                ]}
+            >
+                <Text
+                    variant="headlineSmallSize"
+                >
+                    {localize("your-project-main-directory")}/webpack.config.js (Hazır Ayar)
+                </Text>
+                <CodeViewer
+                    code={defaultWebpackCode}
+                    language="javascript"
+                />
+            </View>
+            <View
+                style={[
+                    stylesheet.borderedCard,
+                    {
+                        backgroundColor: colors.content.container.default,
+                        borderColor: colors.content.border.subtle,
+                        marginBottom: spaces.spacingMd,
+                        padding: spaces.spacingMd
+                    }
+                ]}
+            >
+                <Text
+                    variant="headlineSmallSize"
+                >
+                    {localize("your-project-main-directory")}/webpack.config.js (Sarmalama)
+                </Text>
+                <CodeViewer
+                    code={wrapWebpackCode}
+                    language="javascript"
+                />
+            </View>
+            <Text
+                style={{
+                    marginBottom: spaces.spacingMd
+                }}
+                variant="bodyMediumSize"
+                color="mid"
+            >
+                Geliştirme ortamınızın (ESLint, TS, VSCode vb.) NCore UI Kit ile aynı standartlara sahip olmasını istiyorsanız tek tıkla projenizi yapılandırabilirsiniz:
+            </Text>
+            <View
+                style={[
+                    stylesheet.borderedCard,
+                    {
+                        backgroundColor: colors.content.container.default,
+                        borderColor: colors.content.border.subtle,
+                        marginBottom: spaces.spacingMd,
+                        padding: spaces.spacingMd
+                    }
+                ]}
+            >
+                <Text
+                    variant="headlineSmallSize"
+                >
+                    Terminal
+                </Text>
+                <CodeViewer
+                    code={envSetupCode}
+                    language="bash"
+                />
+            </View>
             <View
                 style={[
                     stylesheet.rowSpaceBetween,

+ 5 - 23
example/web/webpack.config.js

@@ -6,6 +6,8 @@ const CopyPlugin = require("copy-webpack-plugin");
 const webpack = require("webpack");
 const path = require("path");
 
+const { withNCoreUIKitWebpack } = require("../../webpack-config.js");
+
 const appDirectory = path.resolve(__dirname, "./");
 const srcDirectory = path.resolve(__dirname, "../src");
 const rootDirectory = path.resolve(__dirname, "../../");
@@ -57,7 +59,7 @@ const imageLoaderConfiguration = {
     }
 };
 
-module.exports = {
+let config = {
     entry: [
         path.resolve(appDirectory, "index.tsx")
     ],
@@ -119,10 +121,6 @@ module.exports = {
         ]
     },
     plugins: [
-        new webpack.ProvidePlugin({
-            process: "process/browser",
-            Buffer: ["buffer", "Buffer"],
-        }),
         new HtmlWebpackPlugin({
             template: path.resolve(appDirectory, "./public/index.html"),
             filename: "index.html"
@@ -168,31 +166,15 @@ module.exports = {
         })
     ],
     resolve: {
-        fallback: {
-            "crypto": require.resolve("crypto-browserify"),
-            "stream": require.resolve("stream-browserify"),
-            "process": require.resolve("process/browser"),
-            "vm": require.resolve("vm-browserify"),
-            "buffer": require.resolve("buffer/"),
-            "util": require.resolve("util/")
-        },
         symlinks: false,
         alias: {
-            "react-native$": "react-native-web",
             "ncore-ui-kit": path.resolve(rootDirectory, "src/index")
         },
-        extensions: [
-            ".web.tsx",
-            ".web.ts",
-            ".web.js",
-            ".tsx",
-            ".ts",
-            ".js",
-            ".mjs"
-        ],
         modules: [
             path.resolve(rootDirectory, "node_modules"),
             "node_modules"
         ]
     }
 };
+
+module.exports = withNCoreUIKitWebpack(config, webpack);

+ 22 - 0
metro-config.js

@@ -0,0 +1,22 @@
+function withNCoreUIKit(config) {
+    if (!config.resolver) {
+        config.resolver = {};
+    }
+
+    if (!config.resolver.extraNodeModules) {
+        config.resolver.extraNodeModules = {};
+    }
+
+    config.resolver.extraNodeModules = {
+        ...config.resolver.extraNodeModules,
+        crypto: require.resolve("crypto-browserify"),
+        process: require.resolve("process/browser"),
+        stream: require.resolve("stream-browserify")
+    };
+
+    return config;
+}
+
+module.exports = {
+    withNCoreUIKit
+};

+ 24 - 14
package.json

@@ -1,6 +1,6 @@
 {
     "name": "ncore-ui-kit",
-    "version": "1.1.0-alpha.19",
+    "version": "1.1.0",
     "description": "NİBGAT® | NCore - UI Kit for React-Native Mobile Apps.",
     "main": "./lib/module/index.js",
     "types": "./lib/typescript/src/index.d.ts",
@@ -13,23 +13,30 @@
         "./package.json": "./package.json"
     },
     "files": [
-        "src",
-        "lib",
-        "android",
-        "ios",
-        "cpp",
-        "*.podspec",
+        "!android/local.properties",
         "react-native.config.js",
-        "!ios/build",
-        "!android/build",
-        "!android/gradle",
-        "!android/gradlew",
         "!android/gradlew.bat",
-        "!android/local.properties",
-        "!**/__tests__",
+        "eslint-local-rules",
+        "eslint.config.mjs",
+        "webpack-config.js",
         "!**/__fixtures__",
+        "!android/gradlew",
+        "metro-config.js",
+        "!android/gradle",
+        "!android/build",
+        "tsconfig.json",
+        "!**/__tests__",
         "!**/__mocks__",
-        "!**/.*"
+        "!ios/build",
+        "*.podspec",
+        "android",
+        ".vscode",
+        "!**/.*",
+        "bin",
+        "cpp",
+        "ios",
+        "lib",
+        "src"
     ],
     "scripts": {
         "example-mobile": "yarn workspace ncore-ui-kit-example-mobile",
@@ -42,6 +49,9 @@
         "release": "release-it --only-version",
         "build": "yarn clean && yarn lint && yarn type-check-build && yarn prepare && npm publish"
     },
+    "bin": {
+        "ncore-setup": "./bin/setup.js"
+    },
     "keywords": [
         "ncore",
         "nibgat",

+ 3 - 5
src/components/codeViewer/index.tsx

@@ -34,8 +34,8 @@ import {
     CheckIcon,
     CopyIcon
 } from "lucide-react-native";
-import Clipboard from "@react-native-clipboard/clipboard";
 import {
+    setStringToClipboard,
     webStyle
 } from "../../utils";
 import Button from "../button";
@@ -259,11 +259,9 @@ const CodeViewer = ({
                 });
             }
         } else {
-            try {
-                Clipboard.setString(replacedCode);
+            isSuccess = await setStringToClipboard(replacedCode);
 
-                isSuccess = true;
-            } catch {
+            if (!isSuccess) {
                 NCoreUIKitToast.open({
                     title: localize("code-copy-error-to-clipboard")
                 });

+ 14 - 9
src/components/userShortcut/index.tsx

@@ -175,14 +175,19 @@ const UserShortcut = ({
         return <View
             style={stylesheet.contentContainer}
         >
-            <Text
-                variant="labelLargeSize"
-                ellipsizeMode="tail"
-                numberOfLines={1}
-                color="high"
-            >
-                {title}
-            </Text>
+            {
+                title ?
+                    <Text
+                        variant="labelLargeSize"
+                        ellipsizeMode="tail"
+                        numberOfLines={1}
+                        color="high"
+                    >
+                        {title}
+                    </Text>
+                :
+                    null
+            }
             {renderSubTitle()}
         </View>;
     };
@@ -408,7 +413,7 @@ const UserShortcut = ({
                         ...avatarProps,
                         isStatusIndicator: avatarProps?.isStatusIndicator ?? !!resolvedStatusText
                     })}
-                    size="medium"
+                    size={avatarProps?.size || "medium"}
                 />
                 {renderBadges()}
             </View>

+ 7 - 7
src/components/userShortcut/type.ts

@@ -34,23 +34,23 @@ interface IUserShortcutProps {
     statusColor?: keyof NCoreUIKit.TextContentColors;
     spreadBehaviour?: UserShortcutSpreadBehaviour;
     isShowLoginButtonWhenNoSession?: boolean;
-    avatarProps?: Omit<IAvatarProps, "size">;
-    variant?: "default" | "compact";
     isWorkWithCoreComplex?: boolean;
+    variant?: "default" | "compact";
     isShowRegisterButton?: boolean;
     actionButtons?: IButtonProps[];
-    onRegisterPress?: () => void;
     isShowLogoutButton?: boolean;
+    onRegisterPress?: () => void;
     isShowArrowButton?: boolean;
-    onLoginPress?: () => void;
-    maxWidth?: DimensionValue;
+    avatarProps?: IAvatarProps;
     isSessionActive?: boolean;
+    maxWidth?: DimensionValue;
+    onLoginPress?: () => void;
     isDisabled?: boolean;
-    onPress?: () => void;
     badges?: ReactNode[];
+    onPress?: () => void;
     statusText?: string;
     subTitle?: string;
-    title: string;
+    title?: string;
 }
 export type {
     IUserShortcutProps as default

+ 21 - 1
src/core/contexts/coreComplex/cryptoPolyfill.ts

@@ -1,9 +1,22 @@
 import {
     Buffer
 } from "buffer";
-import cryptoModule from "crypto";
+
+let cryptoModule: Record<string, unknown> = {};
+
+try {
+    const req = typeof require !== "undefined" ? require : undefined;
+
+    if (req) {
+        const cryptoStr = "crypto";
+        cryptoModule = (req as (id: string) => Record<string, unknown>)(cryptoStr);
+    }
+} catch {
+    cryptoModule = {};
+}
 
 const _global = typeof window !== "undefined" ? window : (typeof global !== "undefined" ? global : null);
+
 if (_global) {
     (_global as Record<string, unknown>).Buffer = (_global as Record<string, unknown>).Buffer || Buffer;
     (_global as Record<string, unknown>).process = (_global as Record<string, unknown>).process || process;
@@ -27,28 +40,35 @@ if (typeof (cryptoModule as Record<string, unknown>).KeyObject === "undefined")
             const buf = (typeof key === "string" ? Buffer.from(key) : (Buffer.isBuffer(key) ? key : Buffer.from(String(key)))) as Buffer & { type: string, export: () => Buffer };
             buf.type = "secret";
             buf.export = () => buf;
+
             return buf;
         },
         createPrivateKey: function(key: unknown) {
             const keyObj = key as Record<string, unknown>;
             const keyStr = typeof key === "string" ? key : (keyObj?.key ? String(keyObj.key) : (keyObj?.toString ? String(keyObj.toString()) : ""));
+
             if (typeof keyStr === "string" && keyStr.includes("-----BEGIN ")) {
                 const buf = Buffer.from(keyStr) as Buffer & { type: string, export: () => Buffer };
                 buf.type = "private";
                 buf.export = () => buf;
+
                 return buf;
             }
+
             throw new Error("Invalid private key");
         },
         createPublicKey: function(key: unknown) {
             const keyObj = key as Record<string, unknown>;
             const keyStr = typeof key === "string" ? key : (keyObj?.key ? String(keyObj.key) : (keyObj?.toString ? String(keyObj.toString()) : ""));
+
             if (typeof keyStr === "string" && keyStr.includes("-----BEGIN ")) {
                 const buf = Buffer.from(keyStr) as Buffer & { type: string, export: () => Buffer };
                 buf.type = "public";
                 buf.export = () => buf;
+
                 return buf;
             }
+
             throw new Error("Invalid public key");
         },
         KeyObject

+ 40 - 0
src/utils/clipboard.ts

@@ -0,0 +1,40 @@
+interface ClipboardModuleType {
+    setStringAsync?: (text: string) => Promise<boolean | void>;
+    setString?: (text: string) => void;
+}
+
+let ClipboardModule: ClipboardModuleType | null = null;
+
+try {
+    // eslint-disable-next-line @typescript-eslint/no-require-imports
+    ClipboardModule = require("@react-native-clipboard/clipboard").default;
+} catch {
+    try {
+        // eslint-disable-next-line @typescript-eslint/no-require-imports
+        ClipboardModule = require("expo-clipboard");
+    } catch {
+        ClipboardModule = null;
+    }
+}
+
+export const setStringToClipboard = async (text: string): Promise<boolean> => {
+    if (!ClipboardModule) {
+        return false;
+    }
+
+    try {
+        if (ClipboardModule.setStringAsync) {
+            await ClipboardModule.setStringAsync(text);
+            return true;
+        }
+
+        if (ClipboardModule.setString) {
+            ClipboardModule.setString(text);
+            return true;
+        }
+
+        return false;
+    } catch {
+        return false;
+    }
+};

+ 2 - 0
src/utils/index.ts

@@ -98,3 +98,5 @@ export const androidTypographyFixer = (defaultPaletteData: NCoreUIKit.Palette):
         return defaultPaletteData;
     }
 };
+
+export * from "./clipboard";

+ 246 - 0
webpack-config.js

@@ -0,0 +1,246 @@
+/* eslint-disable @typescript-eslint/no-require-imports */
+const path = require("path");
+
+function withNCoreUIKitWebpack(config, webpack) {
+    if(!config.resolve) {
+        config.resolve = {};
+    }
+
+    if(!config.resolve.fallback) {
+        config.resolve.fallback = {};
+    }
+
+    config.resolve.fallback = {
+        ...config.resolve.fallback,
+        crypto: require.resolve("crypto-browserify"),
+        stream: require.resolve("stream-browserify"),
+        process: require.resolve("process/browser"),
+        vm: require.resolve("vm-browserify"),
+        buffer: require.resolve("buffer/"),
+        util: require.resolve("util/")
+    };
+
+    if(!config.resolve.alias) {
+        config.resolve.alias = {};
+    }
+
+    config.resolve.alias = {
+        ...config.resolve.alias,
+        "react-native$": "react-native-web"
+    };
+
+    if(!config.resolve.extensions) {
+        config.resolve.extensions = [];
+    }
+
+    const webExtensions = [
+        ".web.tsx",
+        ".web.ts",
+        ".web.js",
+        ".tsx",
+        ".ts",
+        ".js",
+        ".mjs"
+    ];
+
+    webExtensions.forEach(ext => {
+        if(!config.resolve.extensions.includes(ext)) {
+            config.resolve.extensions.push(ext);
+        }
+    });
+
+    if(!config.plugins) {
+        config.plugins = [];
+    }
+
+    if(webpack) {
+        config.plugins.push(
+            new webpack.ProvidePlugin({
+                process: "process/browser",
+                Buffer: [
+                    "buffer",
+                    "Buffer"
+                ]
+            })
+        );
+    }
+
+    return config;
+}
+
+function createDefaultWebpackConfig({
+    HtmlWebpackPlugin,
+    appDirectory,
+    ESLintPlugin,
+    CopyPlugin,
+    webpack
+}) {
+    const babelLoaderConfiguration = {
+        use: {
+            options: {
+                presets: [
+                    "module:@react-native/babel-preset",
+                    "@babel/preset-typescript"
+                ],
+                plugins: [
+                    "react-native-web"
+                ],
+                cacheDirectory: true
+            },
+            loader: "babel-loader"
+        },
+        include: [
+            path.resolve(appDirectory, "index.tsx"),
+            path.resolve(appDirectory, "src")
+        ],
+        test: /\.(tsx|ts|js|jsx)$/
+    };
+
+    const imageLoaderConfiguration = {
+        test: /\.(gif|jpe?g|png|svg)$/,
+        use: {
+            options: {
+                name: "[name].[ext]",
+                esModule: false
+            },
+            loader: "url-loader"
+        }
+    };
+
+    let config = {
+        plugins: [
+            new webpack.DefinePlugin({
+                "process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV || "development"),
+                __DEV__: JSON.stringify(process.env.NODE_ENV !== "production"),
+                "__REACT_DEVTOOLS_GLOBAL_HOOK__": "({ isDisabled: true })",
+                "process.env.EXPO_OS": JSON.stringify("web"),
+                "process.env": JSON.stringify({})
+            })
+        ],
+        module: {
+            rules: [
+                babelLoaderConfiguration,
+                imageLoaderConfiguration,
+                {
+                    include: /node_modules\/@react-navigation/,
+                    resolve: {
+                        fullySpecified: false
+                    },
+                    test: /\.(js|mjs|jsx)$/,
+                    type: "javascript/auto"
+                },
+                {
+                    include: /node_modules/,
+                    type: "javascript/auto",
+                    test: /\.mjs$/
+                },
+                {
+                    generator: {
+                        filename: "assets/fonts/[name][ext]"
+                    },
+                    test: /\.(ttf|otf|eot|woff|woff2)$/,
+                    type: "asset/resource"
+                }
+            ]
+        },
+        resolve: {
+            modules: [
+                path.resolve(appDirectory, "node_modules"),
+                "node_modules"
+            ],
+            symlinks: false
+        },
+        output: {
+            path: path.resolve(appDirectory, "dist"),
+            filename: "bundle.web.js",
+            publicPath: "/"
+        },
+        entry: [
+            path.resolve(appDirectory, "index.tsx")
+        ],
+        ignoreWarnings: [
+            {
+                module: /lucide-react-native/
+            },
+            {
+                module: /@react-navigation/
+            },
+            {
+                module: /ncore-ui-kit/
+            }
+        ],
+        devServer: {
+            historyApiFallback: true,
+            client: {
+                overlay: {
+                    warnings: false,
+                    errors: true
+                },
+                logging: "none"
+            },
+            port: 3000
+        },
+        devtool: "source-map",
+        mode: "development"
+    };
+
+    if(HtmlWebpackPlugin) {
+        config.plugins.push(
+            new HtmlWebpackPlugin({
+                template: path.resolve(appDirectory, "./public/index.html"),
+                filename: "index.html"
+            })
+        );
+    }
+
+    if(ESLintPlugin) {
+        config.plugins.push(
+            new ESLintPlugin({
+                overrideConfigFile: path.resolve(appDirectory, "eslint.config.mjs"),
+                failOnError: process.env.NODE_ENV === "production",
+                exclude: [
+                    path.resolve(appDirectory, "node_modules"),
+                    path.resolve(appDirectory, "dist")
+                ],
+                eslintPath: "eslint/use-at-your-own-risk",
+                configType: "flat",
+                emitWarning: true,
+                extensions: [
+                    "tsx",
+                    "jsx",
+                    "ts",
+                    "js"
+                ]
+            })
+        );
+    }
+
+    if(CopyPlugin) {
+        config.plugins.push(
+            new CopyPlugin({
+                patterns: [
+                    {
+                        globOptions: {
+                            ignore: [
+                                "**/index.html"
+                            ],
+                            dot: true
+                        },
+                        noErrorOnMissing: true,
+                        from: "public",
+                        to: "."
+                    }
+                ]
+            })
+        );
+    }
+
+    config = withNCoreUIKitWebpack(config, webpack);
+
+    return config;
+}
+
+module.exports = {
+    createDefaultWebpackConfig,
+    withNCoreUIKitWebpack
+};