@vetc-miniapp/ui-react 0.0.2 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@vetc-miniapp/ui-react",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "description": "MiniApp Platform UI React",
5
- "main": "src/index.js",
5
+ "main": "src/ui-react/index.js",
6
6
  "types": "src/ui-react/index.ts",
7
7
  "type": "module",
8
8
  "files": [
@@ -20,8 +20,10 @@
20
20
  "author": "Hieuth052@gmail.com",
21
21
  "license": "MIT",
22
22
  "devDependencies": {
23
+ "@babel/cli": "^7.28.6",
23
24
  "@babel/core": "^7.29.0",
24
25
  "@babel/preset-env": "^7.29.0",
26
+ "@babel/preset-react": "^7.28.5",
25
27
  "@rollup/plugin-commonjs": "^29.0.0",
26
28
  "@rollup/plugin-node-resolve": "^16.0.3",
27
29
  "@rollup/plugin-terser": "^0.4.4",
@@ -31,128 +33,23 @@
31
33
  "ts-loader": "^9.5.4",
32
34
  "typescript": "^5.9.3",
33
35
  "webpack": "^5.104.1",
34
- "webpack-cli": "^6.0.1"
36
+ "webpack-cli": "^6.0.1",
37
+ "react": "^19.0.0",
38
+ "react-dom": "^19.0.0"
35
39
  },
36
40
  "dependencies": {
37
41
  "acorn": "^8.15.0",
38
42
  "acorn-import-phases": "^1.0.4",
39
- "ajv": "^8.17.1",
40
- "ajv-formats": "^2.1.1",
41
- "ajv-keywords": "^5.1.0",
42
43
  "babel-plugin-polyfill-corejs2": "^0.4.15",
43
44
  "babel-plugin-polyfill-corejs3": "^0.14.0",
44
45
  "babel-plugin-polyfill-regenerator": "^0.6.6",
45
46
  "baseline-browser-mapping": "^2.9.19",
46
- "browserslist": "^4.28.1",
47
- "buffer-from": "^1.1.2",
48
- "caniuse-lite": "^1.0.30001767",
49
- "chrome-trace-event": "^1.0.4",
50
- "clone-deep": "^4.0.1",
51
- "colorette": "^2.0.20",
52
- "commander": "^2.20.3",
53
- "commondir": "^1.0.1",
54
- "convert-source-map": "^2.0.0",
55
- "core-js-compat": "^3.48.0",
56
- "cross-spawn": "^7.0.6",
57
- "debug": "^4.4.3",
58
- "deepmerge": "^4.3.1",
59
- "electron-to-chromium": "^1.5.283",
60
- "enhanced-resolve": "^5.18.4",
61
- "envinfo": "^7.21.0",
62
- "es-module-lexer": "^2.0.0",
63
- "escalade": "^3.2.0",
64
- "eslint-scope": "^5.1.1",
65
- "esrecurse": "^4.3.0",
66
- "estraverse": "^4.3.0",
67
- "estree-walker": "^2.0.2",
68
- "esutils": "^2.0.3",
69
- "events": "^3.3.0",
70
- "fast-deep-equal": "^3.1.3",
71
- "fast-uri": "^3.1.0",
72
- "fastest-levenshtein": "^1.0.16",
73
- "fdir": "^6.5.0",
74
- "find-up": "^5.0.0",
75
- "flat": "^5.0.2",
76
- "fsevents": "^2.3.3",
77
- "function-bind": "^1.1.2",
78
- "gensync": "^1.0.0-beta.2",
79
- "glob-to-regexp": "^0.4.1",
80
- "graceful-fs": "^4.2.11",
81
- "has-flag": "^4.0.0",
82
- "hasown": "^2.0.2",
83
- "import-local": "^3.2.0",
84
- "interpret": "^3.1.1",
85
- "is-core-module": "^2.16.1",
86
- "is-module": "^1.0.0",
87
- "is-plain-object": "^2.0.4",
88
- "is-reference": "^1.2.1",
89
- "isexe": "^2.0.0",
90
- "isobject": "^3.0.1",
91
- "jest-worker": "^27.5.1",
92
- "js-tokens": "^4.0.0",
93
- "jsesc": "^3.1.0",
94
- "json-parse-even-better-errors": "^2.3.1",
95
- "json-schema-traverse": "^1.0.0",
96
- "json5": "^2.2.3",
97
- "kind-of": "^6.0.3",
98
- "loader-runner": "^4.3.1",
99
- "locate-path": "^6.0.0",
100
- "lodash.debounce": "^4.0.8",
101
- "lru-cache": "^5.1.1",
102
- "magic-string": "^0.30.21",
103
- "merge-stream": "^2.0.0",
104
- "mime-db": "^1.52.0",
105
- "mime-types": "^2.1.35",
106
- "ms": "^2.1.3",
107
- "neo-async": "^2.6.2",
108
- "node-releases": "^2.0.27",
109
- "p-limit": "^3.1.0",
110
- "p-locate": "^5.0.0",
111
- "p-try": "^2.2.0",
112
- "path-exists": "^4.0.0",
113
- "path-key": "^3.1.1",
114
- "path-parse": "^1.0.7",
115
- "picocolors": "^1.1.1",
116
- "picomatch": "^4.0.3",
117
- "pkg-dir": "^4.2.0",
118
- "randombytes": "^2.1.0",
119
- "rechoir": "^0.8.0",
120
- "regenerate": "^1.4.2",
121
- "regenerate-unicode-properties": "^10.2.2",
122
- "regexpu-core": "^6.4.0",
123
- "regjsgen": "^0.8.0",
124
- "regjsparser": "^0.13.0",
125
- "require-from-string": "^2.0.2",
126
- "resolve": "^1.22.11",
127
- "resolve-cwd": "^3.0.0",
128
- "resolve-from": "^5.0.0",
129
- "safe-buffer": "^5.2.1",
130
- "schema-utils": "^4.3.3",
131
- "semver": "^6.3.1",
132
- "serialize-javascript": "^6.0.2",
133
- "shallow-clone": "^3.0.1",
134
- "shebang-command": "^2.0.0",
135
- "shebang-regex": "^3.0.0",
136
- "smob": "^1.5.0",
137
- "source-map": "^0.6.1",
138
- "source-map-support": "^0.5.21",
139
- "supports-color": "^8.1.1",
140
- "supports-preserve-symlinks-flag": "^1.0.0",
141
- "tapable": "^2.3.0",
142
- "terser": "^5.46.0",
143
- "terser-webpack-plugin": "^5.3.16",
144
- "undici-types": "^7.16.0",
145
- "unicode-canonical-property-names-ecmascript": "^2.0.1",
146
- "unicode-match-property-ecmascript": "^2.0.0",
147
- "unicode-match-property-value-ecmascript": "^2.2.1",
148
- "unicode-property-aliases-ecmascript": "^2.2.0",
149
- "update-browserslist-db": "^1.2.3",
150
47
  "watchpack": "^2.5.1",
151
48
  "webpack-merge": "^6.0.1",
152
- "webpack-sources": "^3.3.3",
153
- "which": "^2.0.2",
154
- "wildcard": "^2.0.1",
155
- "yallist": "^3.1.1",
156
- "yocto-queue": "^0.1.0"
49
+ "webpack-sources": "^3.3.3"
50
+ },
51
+ "peerDependencies": {
52
+ "react": ">=19.0.0",
53
+ "react-dom": ">=19.0.0"
157
54
  }
158
55
  }
@@ -1 +1,2 @@
1
- !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports["vetc-miniapp/ui-react"]=t():e["vetc-miniapp/ui-react"]=t()}(this,()=>(()=>{"use strict";var e={d:(t,o)=>{for(var n in o)e.o(o,n)&&!e.o(t,n)&&Object.defineProperty(t,n,{enumerable:!0,get:o[n]})},o:(e,t)=>Object.prototype.hasOwnProperty.call(e,t),r:e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})}},t={};e.r(t),e.d(t,{useNavigate:()=>i});const o="undefined"!=typeof window,n=(e,t={})=>{const n=o&&window.flutter_inappwebview?window.flutter_inappwebview:null;return n?n.callHandler("MiniAppBridge",{action:e,payload:t}):Promise.reject(new Error("MiniApp bridge not available"))};function i(){return(e,t={},o={})=>{n("navigate",{type:"native",route:e,params:t,options:o})}}return t})());
1
+ /*! For license information please see index.js.LICENSE.txt */
2
+ !function(e,r){"object"==typeof exports&&"object"==typeof module?module.exports=r(require("react")):"function"==typeof define&&define.amd?define(["react"],r):"object"==typeof exports?exports["vetc-miniapp/ui-react"]=r(require("react")):e["vetc-miniapp/ui-react"]=r(e.react)}(this,e=>(()=>{"use strict";var r={155(r){r.exports=e},698(e,r){var t=Symbol.for("react.transitional.element"),n=Symbol.for("react.fragment");function o(e,r,n){var o=null;if(void 0!==n&&(o=""+n),void 0!==r.key&&(o=""+r.key),"key"in r)for(var i in n={},r)"key"!==i&&(n[i]=r[i]);else n=r;return r=n.ref,{$$typeof:t,type:e,key:o,ref:void 0!==r?r:null,props:n}}r.Fragment=n,r.jsx=o,r.jsxs=o},848(e,r,t){e.exports=t(698)}},t={};function n(e){var o=t[e];if(void 0!==o)return o.exports;var i=t[e]={exports:{}};return r[e](i,i.exports,n),i.exports}n.d=(e,r)=>{for(var t in r)n.o(r,t)&&!n.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:r[t]})},n.o=(e,r)=>Object.prototype.hasOwnProperty.call(e,r),n.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})};var o={};n.r(o),n.d(o,{App:()=>m,appConfig:()=>u,getCurrentPage:()=>y,initRouter:()=>v,setCurrentPath:()=>g,useNavigate:()=>l});var i="undefined"!=typeof window,a=function(e){var r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},t=i&&window.flutter_inappwebview?window.flutter_inappwebview:null;return t?t.callHandler("MiniAppBridge",{action:e,payload:r}):Promise.reject(new Error("MiniApp bridge not available"))};function l(){return function(e){a("navigate",{type:"native",route:e,params:arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},options:arguments.length>2&&void 0!==arguments[2]?arguments[2]:{}})}}var u={locale:"vi",theme:"light",pages:[]},c=n(155),f=n(848);function p(e,r){(null==r||r>e.length)&&(r=e.length);for(var t=0,n=Array(r);t<r;t++)n[t]=e[t];return n}var s=new Map,d="/";function v(e){console.log(e),s=new Map(e.pages.map(function(e){return[e.path,e]})),console.log(s)}function y(){return s.get(d)}function g(e){d=e}function m(e){var r,t,n=e.config,o=e.children,i=(r=(0,c.useState)(null),t=2,function(e){if(Array.isArray(e))return e}(r)||function(e,r){var t=null==e?null:"undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(null!=t){var n,o,i,a,l=[],u=!0,c=!1;try{if(i=(t=t.call(e)).next,0===r){if(Object(t)!==t)return;u=!1}else for(;!(u=(n=i.call(t)).done)&&(l.push(n.value),l.length!==r);u=!0);}catch(e){c=!0,o=e}finally{try{if(!u&&null!=t.return&&(a=t.return(),Object(a)!==a))return}finally{if(c)throw o}}return l}}(r,t)||function(e,r){if(e){if("string"==typeof e)return p(e,r);var t={}.toString.call(e).slice(8,-1);return"Object"===t&&e.constructor&&(t=e.constructor.name),"Map"===t||"Set"===t?Array.from(e):"Arguments"===t||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t)?p(e,r):void 0}}(r,t)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()),l=i[0],u=i[1];(0,c.useEffect)(function(){var e;if(console.warn("SDK useEffect fired"),null!=n&&null!==(e=n.pages)&&void 0!==e&&e.length){v(n),console.warn(n),g((window.location.path||"/").replace("/miniapp","")||"/");var r=y();console.warn(r),r||(g("/"),r=y()),u(r),a("registerAppConfig",{config:n})}},[n]),console.warn("SDK fired"),l&&l.Component||console.warn("MiniApp: Page not found for current route");var s=l.Component||(0,f.jsx)("div",{});return(0,f.jsxs)(f.Fragment,{children:[(0,f.jsx)(s,{}),o]})}return o})());
@@ -0,0 +1,9 @@
1
+ /**
2
+ * @license React
3
+ * react-jsx-runtime.production.js
4
+ *
5
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
6
+ *
7
+ * This source code is licensed under the MIT license found in the
8
+ * LICENSE file in the root directory of this source tree.
9
+ */
@@ -0,0 +1,7 @@
1
+ import { ComponentPropsWithRef, ReactNode } from 'react';
2
+ import { IAppConfig } from '../types/app';
3
+
4
+ export type IAppProps = ComponentPropsWithRef<'div'> & {
5
+ config: IAppConfig;
6
+ };
7
+ export declare const App: ({ className, config, localesConfig, ...props }: IAppProps) => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,75 @@
1
+ // "use client";
2
+
3
+ import React, { useEffect, useState } from "react";
4
+ import { callHost } from "../bridge";
5
+ let routeMap = new Map()
6
+ let currentPath = "/"
7
+
8
+ export function initRouter(config) {
9
+ console.log(config);
10
+ routeMap = new Map(config.pages.map(p => [p.path, p]))
11
+ console.log(routeMap);
12
+ }
13
+
14
+ export function getCurrentPage() {
15
+ return routeMap.get(currentPath)
16
+ }
17
+
18
+ export function setCurrentPath(path) {
19
+ currentPath = path
20
+ }
21
+
22
+ /**
23
+ * MiniApp Runtime Root
24
+ */
25
+ export function App({ config, children }) {
26
+ const [page, setPage] = useState(null);
27
+
28
+ useEffect(() => {
29
+ console.warn("SDK useEffect fired");
30
+
31
+ if (!config?.pages?.length) return;
32
+
33
+ // 1. Init router từ config
34
+ initRouter(config);
35
+ console.warn(config);
36
+
37
+ // 2. Lấy pathname từ URL WebView
38
+ const rawPath = window.location.path || "/";
39
+ const path = rawPath.replace("/miniapp", "") || "/";
40
+
41
+ setCurrentPath(path);
42
+
43
+ // 3. Lấy page tương ứng
44
+ let current = getCurrentPage();
45
+ console.warn(current);
46
+
47
+ // 4. Fallback về root nếu không match
48
+ if (!current) {
49
+ setCurrentPath("/");
50
+ current = getCurrentPage();
51
+ }
52
+
53
+ setPage(current);
54
+
55
+ // 5. Gửi config sang Flutter để dựng native header
56
+ callHost("registerAppConfig", { config });
57
+ }, [config]);
58
+
59
+ console.warn("SDK fired");
60
+
61
+ // Tránh crash nếu chưa có page
62
+ if (!page || !page.Component) {
63
+ console.warn("MiniApp: Page not found for current route");
64
+ // return null;
65
+ }
66
+
67
+ const Component = page.Component || <div />;
68
+
69
+ return (
70
+ <>
71
+ <Component />
72
+ {children}
73
+ </>
74
+ );
75
+ }
@@ -0,0 +1,42 @@
1
+ import { useEffect, useState } from "react";
2
+ let routeMap = new Map()
3
+ let currentPath = "/"
4
+
5
+ export function initRouter(config) {
6
+ routeMap = new Map(config.pages.map(p => [p.pathname, p]))
7
+ }
8
+
9
+ export function getCurrentPage() {
10
+ return routeMap.get(currentPath)
11
+ }
12
+
13
+ export function setCurrentPath(path) {
14
+ currentPath = path
15
+ }
16
+
17
+ export function App({ config }) {
18
+ const [page, setPage] = useState(null)
19
+
20
+ useEffect(() => {
21
+ initRouter(config)
22
+
23
+ const rawPath = window.location.pathname || "/"
24
+ const path = rawPath.replace("/miniapp", "") || "/"
25
+
26
+ setCurrentPath(path)
27
+
28
+ let current = getCurrentPage()
29
+ if (!current) {
30
+ setCurrentPath("/")
31
+ current = getCurrentPage()
32
+ }
33
+
34
+ setPage(current)
35
+
36
+ callHost("registerAppConfig", { config })
37
+ }, [config])
38
+
39
+ if (!page) return <div />
40
+ const Component = page.Component
41
+ return <Component />
42
+ }
@@ -0,0 +1,101 @@
1
+ //'use client';
2
+
3
+ /**
4
+ * @typedef {import('../types/app').IAppConfig} IAppConfig
5
+ */
6
+
7
+ /**
8
+ * @typedef {Object} IAppProps
9
+ * @property {IAppConfig} config
10
+ * @property {string=} className
11
+ */
12
+
13
+ /**
14
+ * Root MiniApp wrapper
15
+ * @param {IAppProps & React.HTMLAttributes<HTMLDivElement>} props
16
+ */
17
+ //import React from "react";
18
+ 'use client';
19
+
20
+ import { useEffect } from "react";
21
+ import {callHost} from '../bridge.js';
22
+ ////import MiniApp from "miniapp-sdk";
23
+
24
+ /**
25
+ * Root MiniApp wrapper
26
+ */
27
+ export function App({ className, config, localesConfig, ...props }) {
28
+
29
+ useEffect(() => {
30
+ // 🚀 Gửi config sang Host khi MiniApp mount
31
+ if (config) {
32
+ console.log(config);
33
+ callHost("registerAppConfig", { config }).catch(console.error);
34
+ }
35
+ //
36
+ // // 🎯 Ví dụ lắng nghe event từ Host
37
+ // const handleStateUpdate = (data) => {
38
+ // console.log("App state updated from host:", data);
39
+ // };
40
+ //
41
+ // MiniApp.on?.("APP_STATE_UPDATE", handleStateUpdate);
42
+ //
43
+ // return () => {
44
+ // MiniApp.off?.("APP_STATE_UPDATE", handleStateUpdate);
45
+ // };
46
+ }, [config]);
47
+
48
+ return props.children;
49
+ }
50
+
51
+ //'use client'
52
+ //
53
+ //import React, { createContext, useContext, useMemo, useState, useEffect } from "react"
54
+ //import {callHost} from '../bridge.js';
55
+ //
56
+ //const RouterCtx = createContext(null)
57
+ //
58
+ //export function MiniRouterProvider({ config, initialPath, children }) {
59
+ // const [path, setPath] = useState(initialPath || "/")
60
+ //
61
+ // const routeMap = useMemo(
62
+ // () => new Map(config.pages.map(p => [p.pathname, p])),
63
+ // [config.pages]
64
+ // )
65
+ //
66
+ // const value = useMemo(() => ({ path, setPath, routeMap }), [path, routeMap])
67
+ //
68
+ // return <RouterCtx.Provider value={value}>{children}</RouterCtx.Provider>
69
+ //}
70
+ //
71
+ //export function useMiniRouter() {
72
+ // const ctx = useContext(RouterCtx)
73
+ // if (!ctx) throw new Error("useMiniRouter must be used inside MiniRouterProvider")
74
+ // return ctx
75
+ //}
76
+ //
77
+ //function Renderer({ config }) {
78
+ // const { path, routeMap } = useMiniRouter()
79
+ // const page = routeMap.get(path)
80
+ //
81
+ // useEffect(() => {
82
+ // if (!page) return
83
+ // callHost("registerAppConfig", {
84
+ // config
85
+ // })
86
+ // }, [path, page])
87
+ //
88
+ // if (!page) return <div>Page not found</div>
89
+ // const Component = page.Component
90
+ // return <Component />
91
+ //}
92
+ //
93
+ //export function App({ config, initialPath, ...props }) {
94
+ // return (
95
+ // <MiniRouterProvider config={config} initialPath={initialPath}>
96
+ // <Renderer config={config}>{props.children}</Renderer>
97
+ // </MiniRouterProvider>
98
+ // )
99
+ //}
100
+
101
+
@@ -0,0 +1,35 @@
1
+ /**
2
+ * @typedef {"light" | "dark"} ThemeMode
3
+ */
4
+
5
+ /**
6
+ * @typedef {Object} IAppState
7
+ * @property {string=} locale
8
+ * @property {ThemeMode=} theme
9
+ */
10
+
11
+ /**
12
+ * @typedef {Object} IPageConfig
13
+ * @property {string} key - Key định danh page dùng cho navigation
14
+ * @property {string=} title - Tên hiển thị (header native)
15
+ * @property {string} url - URL nội bộ miniapp (nếu cần render state)
16
+ * @property {boolean=} canGoBack - Có cho phép back không
17
+ * @property {boolean=} isRoot - Có phải root page không
18
+ * @property {Object<string, any>=} defaultParams - Params mặc định khi mở page
19
+ */
20
+
21
+ /**
22
+ * @typedef {Object} IAppConfig
23
+ * @property {IPageConfig[]} pages
24
+ * @property {string=} locale
25
+ * @property {ThemeMode=} theme
26
+ */
27
+
28
+ /** @type {IAppConfig} */
29
+ export const appConfig = {
30
+ locale: "vi",
31
+ theme: "light",
32
+
33
+ pages: [
34
+ ]
35
+ };
@@ -0,0 +1,8 @@
1
+ import { Dispatch, SetStateAction } from 'react';
2
+ import { IAppState, IAppConfig } from '../types/app';
3
+
4
+ export declare const useAppState: () => {
5
+ config: IAppConfig;
6
+ appState: IAppState;
7
+ setAppState: Dispatch<SetStateAction<IAppState>>;
8
+ };
@@ -1 +1,3 @@
1
1
  export * from './hooks/use-navigate.js';
2
+ export * from './hooks/use-app-state.js';
3
+ export * from './components/app.jsx';
@@ -1 +1,4 @@
1
1
  export * from './hooks/use-navigate';
2
+ export * from './hooks/use-app-state';
3
+ // export * from './components/app';
4
+
@@ -0,0 +1,30 @@
1
+ /**
2
+ * @typedef {"light" | "dark"} ThemeMode
3
+ */
4
+
5
+ /**
6
+ * Trạng thái cấp App do Host cung cấp
7
+ * @typedef {Object} IAppState
8
+ * @property {string=} locale
9
+ * @property {ThemeMode=} theme
10
+ */
11
+
12
+ /**
13
+ * Cấu hình 1 page trong MiniApp
14
+ * @typedef {Object} IPageConfig
15
+ * @property {string} key - Key định danh page dùng cho navigation
16
+ * @property {string=} title - Tên hiển thị (header native)
17
+ * @property {string} url - URL nội bộ của page (phục vụ render state)
18
+ * @property {boolean=} canGoBack - Có cho phép back không
19
+ * @property {boolean=} isRoot - Có phải root page không
20
+ * @property {Object<string, any>=} defaultParams - Params mặc định khi mở page
21
+ */
22
+
23
+ /**
24
+ * Cấu hình tổng của MiniApp
25
+ * @typedef {IAppState & {
26
+ * pages: IPageConfig[]
27
+ * }} IAppConfig
28
+ */
29
+
30
+ export {}; // để file được coi là module
@@ -0,0 +1,32 @@
1
+
2
+ export type IAppConfig = {
3
+ pages: IPageConfig[];
4
+ // darkMode?: IDarkModeConfig;
5
+ } & IAppState;
6
+
7
+
8
+ export type IAppState = {
9
+ locale?: string;
10
+ theme?: "light" | "dark";
11
+ };
12
+
13
+ export type IPageConfig = {
14
+ /** Key định danh page dùng cho navigation */
15
+ key: string;
16
+
17
+ /** Tên hiển thị (optional, dùng cho header native) */
18
+ title?: string;
19
+
20
+ page: React.ComponentType<any>
21
+
22
+ url: string;
23
+
24
+ /** Có cho phép back không */
25
+ canGoBack?: boolean;
26
+
27
+ /** Có phải root page không */
28
+ isRoot?: boolean;
29
+
30
+ /** Params mặc định khi mở page */
31
+ defaultParams?: Record<string, unknown>;
32
+ };