@arcote.tech/platform 0.4.1
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 +39 -0
- package/src/arc.ts +308 -0
- package/src/hooks/use-title.ts +11 -0
- package/src/i18n.tsx +76 -0
- package/src/index.ts +121 -0
- package/src/layout/blank-layout.tsx +15 -0
- package/src/layout/layout-provider.tsx +39 -0
- package/src/layout/overflow-nav.tsx +164 -0
- package/src/layout/page-router.tsx +130 -0
- package/src/layout/page-sub-nav-shell.tsx +23 -0
- package/src/layout/slot-renderer.tsx +70 -0
- package/src/layout/use-slot-fragments.ts +20 -0
- package/src/locale.tsx +64 -0
- package/src/module-loader.ts +102 -0
- package/src/platform-app.tsx +208 -0
- package/src/platform-context.tsx +45 -0
- package/src/registry.ts +238 -0
- package/src/router.tsx +23 -0
- package/src/theme.tsx +72 -0
- package/src/types.ts +165 -0
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@arcote.tech/platform",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "0.4.1",
|
|
5
|
+
"private": false,
|
|
6
|
+
"author": "Przemysław Krasiński [arcote.tech]",
|
|
7
|
+
"description": "Arc Platform — module system, router, layout, theme, i18n, platform app shell",
|
|
8
|
+
"main": "./src/index.ts",
|
|
9
|
+
"types": "./src/index.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./src/index.ts",
|
|
13
|
+
"import": "./src/index.ts",
|
|
14
|
+
"default": "./src/index.ts"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"scripts": {
|
|
18
|
+
"type-check": "tsc --noEmit"
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"@arcote.tech/arc-ds": "workspace:*",
|
|
22
|
+
"@arcote.tech/arc-react": "workspace:*"
|
|
23
|
+
},
|
|
24
|
+
"peerDependencies": {
|
|
25
|
+
"@arcote.tech/arc": "workspace:*",
|
|
26
|
+
"@lingui/core": "^5.0.0",
|
|
27
|
+
"@lingui/react": "^5.0.0",
|
|
28
|
+
"framer-motion": "^12.0.0",
|
|
29
|
+
"lucide-react": ">=0.400.0",
|
|
30
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
31
|
+
"react-dom": "^18.0.0 || ^19.0.0",
|
|
32
|
+
"typescript": "^5.0.0"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@lingui/core": "^5.9.2",
|
|
36
|
+
"@types/react": "^19.2.7",
|
|
37
|
+
"typescript": "~5.9.3"
|
|
38
|
+
}
|
|
39
|
+
}
|
package/src/arc.ts
ADDED
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
import type { ComponentType, ReactNode } from "react";
|
|
2
|
+
import { context as createContext, type ArcContextElementAny } from "@arcote.tech/arc";
|
|
3
|
+
import { registerModule, setContext, setVariantOverrides } from "./registry";
|
|
4
|
+
import type {
|
|
5
|
+
ArcComponent,
|
|
6
|
+
ArcFragment,
|
|
7
|
+
ArcModule,
|
|
8
|
+
BuiltModule,
|
|
9
|
+
ContextElementFragment,
|
|
10
|
+
ExtractPages,
|
|
11
|
+
PageFragment,
|
|
12
|
+
PageOptions,
|
|
13
|
+
SlotFragment,
|
|
14
|
+
SlotId,
|
|
15
|
+
SlotOptions,
|
|
16
|
+
WrapperFragment,
|
|
17
|
+
WrapperOptions,
|
|
18
|
+
} from "./types";
|
|
19
|
+
|
|
20
|
+
let moduleCounter = 0;
|
|
21
|
+
let fragmentCounter = 0;
|
|
22
|
+
|
|
23
|
+
function nextModuleId(): string {
|
|
24
|
+
return `mod_${++moduleCounter}`;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function nextFragmentId(prefix: string): string {
|
|
28
|
+
return `${prefix}_${++fragmentCounter}`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function isImpl(this: { types: readonly string[] }, type: string): boolean {
|
|
32
|
+
return this.types.includes(type);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Detect if a value is an ArcContext (has elements array + elementMap).
|
|
37
|
+
*/
|
|
38
|
+
function isArcContext(value: unknown): boolean {
|
|
39
|
+
return (
|
|
40
|
+
value != null &&
|
|
41
|
+
typeof value === "object" &&
|
|
42
|
+
"elements" in value &&
|
|
43
|
+
"elementMap" in value &&
|
|
44
|
+
Array.isArray((value as any).elements)
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
// Standalone fragment factories
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
|
|
52
|
+
export function page<TPath extends string>(
|
|
53
|
+
path: TPath,
|
|
54
|
+
component: ArcComponent,
|
|
55
|
+
options: PageOptions = {},
|
|
56
|
+
): PageFragment<TPath> {
|
|
57
|
+
return {
|
|
58
|
+
id: options.id ?? nextFragmentId("page"),
|
|
59
|
+
moduleId: "",
|
|
60
|
+
types: ["page"] as const,
|
|
61
|
+
is: isImpl,
|
|
62
|
+
path,
|
|
63
|
+
component,
|
|
64
|
+
layout: options.layout,
|
|
65
|
+
icon: options.icon,
|
|
66
|
+
label: options.label,
|
|
67
|
+
tooltip: options.tooltip,
|
|
68
|
+
children: options.children,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function wrapper(
|
|
73
|
+
component: ComponentType<{ children: ReactNode }>,
|
|
74
|
+
options: WrapperOptions = {},
|
|
75
|
+
): WrapperFragment {
|
|
76
|
+
const { id, order, after, before } = options;
|
|
77
|
+
return {
|
|
78
|
+
id: id ?? nextFragmentId("wrap"),
|
|
79
|
+
moduleId: "",
|
|
80
|
+
types: ["wrapper"] as const,
|
|
81
|
+
is: isImpl,
|
|
82
|
+
component,
|
|
83
|
+
ordering: { order: order ?? 0, after, before },
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function slot(
|
|
88
|
+
slotId: SlotId | string,
|
|
89
|
+
component: ArcComponent,
|
|
90
|
+
options: SlotOptions = {},
|
|
91
|
+
): SlotFragment {
|
|
92
|
+
const { id, order, after, before } = options;
|
|
93
|
+
return {
|
|
94
|
+
id: id ?? nextFragmentId("slot"),
|
|
95
|
+
moduleId: "",
|
|
96
|
+
types: ["slot"] as const,
|
|
97
|
+
is: isImpl,
|
|
98
|
+
slotId: slotId as SlotId,
|
|
99
|
+
component,
|
|
100
|
+
ordering: { order: order ?? 0, after, before },
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function contextElement(
|
|
105
|
+
element: ArcContextElementAny,
|
|
106
|
+
): ContextElementFragment {
|
|
107
|
+
return {
|
|
108
|
+
id: element.name,
|
|
109
|
+
moduleId: "",
|
|
110
|
+
types: ["context-element"] as const,
|
|
111
|
+
is: isImpl,
|
|
112
|
+
element,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Convert all elements from a context into ContextElementFragments.
|
|
118
|
+
*
|
|
119
|
+
* Shorthand for `context.elements.map(contextElement)`.
|
|
120
|
+
*
|
|
121
|
+
* @example
|
|
122
|
+
* ```typescript
|
|
123
|
+
* module("auth")
|
|
124
|
+
* .private([
|
|
125
|
+
* ...contextFragments(authContext),
|
|
126
|
+
* wrapper(AuthProvider, { order: 0 }),
|
|
127
|
+
* ])
|
|
128
|
+
* .build();
|
|
129
|
+
* ```
|
|
130
|
+
*/
|
|
131
|
+
export function contextFragments(
|
|
132
|
+
ctx: { elements: readonly ArcContextElementAny[] },
|
|
133
|
+
): ContextElementFragment[] {
|
|
134
|
+
return ctx.elements.map(contextElement);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// ---------------------------------------------------------------------------
|
|
138
|
+
// module() — primary API for creating Arc modules
|
|
139
|
+
// ---------------------------------------------------------------------------
|
|
140
|
+
|
|
141
|
+
class ModuleBuilder<
|
|
142
|
+
TPages = {},
|
|
143
|
+
TCtx = undefined,
|
|
144
|
+
TScope extends string | undefined = undefined,
|
|
145
|
+
> {
|
|
146
|
+
private _public: ArcFragment[] = [];
|
|
147
|
+
private _private: ArcFragment[] = [];
|
|
148
|
+
private _pages: Record<string, string> | undefined;
|
|
149
|
+
private _ctx: { elements: readonly ArcContextElementAny[] } | undefined;
|
|
150
|
+
private _scope: string | undefined;
|
|
151
|
+
|
|
152
|
+
constructor(readonly name: string) {}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Register the module's ArcContext and scope name.
|
|
156
|
+
* Context element fragments are auto-injected in build().
|
|
157
|
+
*/
|
|
158
|
+
context<C extends { elements: readonly ArcContextElementAny[] }, S extends string>(
|
|
159
|
+
ctx: C,
|
|
160
|
+
scope: S,
|
|
161
|
+
): ModuleBuilder<TPages, C, S> {
|
|
162
|
+
this._ctx = ctx;
|
|
163
|
+
this._scope = scope;
|
|
164
|
+
return this as unknown as ModuleBuilder<TPages, C, S>;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Register public fragments.
|
|
169
|
+
*
|
|
170
|
+
* Array: existing behavior, no named pages.
|
|
171
|
+
* Record: keys become `pages.*` on the built module.
|
|
172
|
+
*/
|
|
173
|
+
public(fragments: ArcFragment[]): ModuleBuilder<TPages, TCtx, TScope>;
|
|
174
|
+
public<R extends Record<string, PageFragment<string>>>(
|
|
175
|
+
pages: R,
|
|
176
|
+
): ModuleBuilder<ExtractPages<R>, TCtx, TScope>;
|
|
177
|
+
public(input: ArcFragment[] | Record<string, PageFragment<string>>): ModuleBuilder<any, TCtx, TScope> {
|
|
178
|
+
if (Array.isArray(input)) {
|
|
179
|
+
this._public = input;
|
|
180
|
+
} else {
|
|
181
|
+
const pages: Record<string, string> = {};
|
|
182
|
+
const fragments: ArcFragment[] = [];
|
|
183
|
+
for (const [key, frag] of Object.entries(input)) {
|
|
184
|
+
pages[key] = frag.path;
|
|
185
|
+
fragments.push(frag);
|
|
186
|
+
}
|
|
187
|
+
this._public = fragments;
|
|
188
|
+
this._pages = pages;
|
|
189
|
+
}
|
|
190
|
+
return this;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
private(fragments: ArcFragment[]): this {
|
|
194
|
+
this._private = fragments;
|
|
195
|
+
return this;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
build(): BuiltModule<TPages, TCtx, TScope>;
|
|
199
|
+
build<D extends Record<string, unknown>>(domain: D): BuiltModule<TPages, TCtx, TScope> & Readonly<D>;
|
|
200
|
+
build(domain?: Record<string, unknown>) {
|
|
201
|
+
const moduleId = nextModuleId();
|
|
202
|
+
|
|
203
|
+
// Auto-inject context element fragments when .context() was called
|
|
204
|
+
if (this._ctx) {
|
|
205
|
+
const ctxFrags = contextFragments(this._ctx);
|
|
206
|
+
this._private = [...ctxFrags, ...this._private];
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const allFragments = [...this._public, ...this._private];
|
|
210
|
+
|
|
211
|
+
// Assign moduleId to all fragments
|
|
212
|
+
for (const f of allFragments) {
|
|
213
|
+
(f as { moduleId: string }).moduleId = moduleId;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Auto-extract and register context from context-element fragments
|
|
217
|
+
const contextElements = allFragments
|
|
218
|
+
.filter((f): f is ContextElementFragment => f.is("context-element"))
|
|
219
|
+
.map((f) => f.element);
|
|
220
|
+
|
|
221
|
+
if (contextElements.length > 0) {
|
|
222
|
+
const ctx = createContext(contextElements);
|
|
223
|
+
setContext(ctx);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const mod: Record<string, unknown> = {
|
|
227
|
+
id: moduleId,
|
|
228
|
+
fragments: allFragments,
|
|
229
|
+
publicFragments: this._public,
|
|
230
|
+
pages: Object.freeze(this._pages ?? {}),
|
|
231
|
+
};
|
|
232
|
+
if (this._ctx !== undefined) mod.context = this._ctx;
|
|
233
|
+
if (this._scope !== undefined) mod.scope = this._scope;
|
|
234
|
+
if (domain) Object.assign(mod, domain);
|
|
235
|
+
|
|
236
|
+
registerModule(mod as unknown as ArcModule);
|
|
237
|
+
return mod;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export function module(name: string): ModuleBuilder {
|
|
242
|
+
return new ModuleBuilder(name);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// ---------------------------------------------------------------------------
|
|
246
|
+
// arc() — deprecated, use module() instead
|
|
247
|
+
// ---------------------------------------------------------------------------
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* @deprecated Use module("name").public([]).private([]).build() instead.
|
|
251
|
+
*/
|
|
252
|
+
export function arc(
|
|
253
|
+
factory: (methods: {
|
|
254
|
+
slot: (slotId: SlotId | string, component: ArcComponent, options?: SlotOptions) => SlotFragment;
|
|
255
|
+
page: <TPath extends string>(path: TPath, component: ArcComponent, options?: PageOptions) => PageFragment<TPath>;
|
|
256
|
+
wrapper: (component: ComponentType<{ children: ReactNode }>, options?: WrapperOptions) => WrapperFragment;
|
|
257
|
+
}) => ArcFragment[] | void,
|
|
258
|
+
): ArcModule {
|
|
259
|
+
const moduleId = nextModuleId();
|
|
260
|
+
const allFragments: ArcFragment[] = [];
|
|
261
|
+
|
|
262
|
+
const methods = {
|
|
263
|
+
slot: (slotId: SlotId | string, component: ArcComponent, options: SlotOptions = {}): SlotFragment => {
|
|
264
|
+
const f = slot(slotId, component, options);
|
|
265
|
+
(f as { moduleId: string }).moduleId = moduleId;
|
|
266
|
+
allFragments.push(f);
|
|
267
|
+
return f;
|
|
268
|
+
},
|
|
269
|
+
page: <TPath extends string>(path: TPath, component: ArcComponent, options: PageOptions = {}): PageFragment<TPath> => {
|
|
270
|
+
const f = page(path, component, options);
|
|
271
|
+
(f as { moduleId: string }).moduleId = moduleId;
|
|
272
|
+
allFragments.push(f);
|
|
273
|
+
return f;
|
|
274
|
+
},
|
|
275
|
+
wrapper: (component: ComponentType<{ children: ReactNode }>, options: WrapperOptions = {}): WrapperFragment => {
|
|
276
|
+
const f = wrapper(component, options);
|
|
277
|
+
(f as { moduleId: string }).moduleId = moduleId;
|
|
278
|
+
allFragments.push(f);
|
|
279
|
+
return f;
|
|
280
|
+
},
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
const result = factory(methods);
|
|
284
|
+
|
|
285
|
+
// Context registration: factory returned an ArcContext
|
|
286
|
+
if (isArcContext(result)) {
|
|
287
|
+
setContext(result);
|
|
288
|
+
return { id: moduleId, fragments: [], publicFragments: [] };
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const publicFragments: readonly ArcFragment[] = Array.isArray(result) ? result : [];
|
|
292
|
+
const mod: ArcModule = {
|
|
293
|
+
id: moduleId,
|
|
294
|
+
fragments: allFragments,
|
|
295
|
+
publicFragments,
|
|
296
|
+
};
|
|
297
|
+
registerModule(mod);
|
|
298
|
+
return mod;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Register DS variant overrides from a module.
|
|
303
|
+
*/
|
|
304
|
+
arc.variants = (
|
|
305
|
+
overrides: Record<string, Record<string, Record<string, string>>>,
|
|
306
|
+
): void => {
|
|
307
|
+
setVariantOverrides(overrides);
|
|
308
|
+
};
|
package/src/i18n.tsx
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createContext,
|
|
3
|
+
useCallback,
|
|
4
|
+
useContext,
|
|
5
|
+
useEffect,
|
|
6
|
+
useState,
|
|
7
|
+
type ReactNode,
|
|
8
|
+
} from "react";
|
|
9
|
+
|
|
10
|
+
interface I18nState {
|
|
11
|
+
locale: string;
|
|
12
|
+
messages: Record<string, string>;
|
|
13
|
+
setLocale: (locale: string) => void;
|
|
14
|
+
locales: Record<string, string>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const I18nContext = createContext<I18nState | null>(null);
|
|
18
|
+
|
|
19
|
+
export interface I18nProviderProps {
|
|
20
|
+
children: ReactNode;
|
|
21
|
+
defaultLocale: string;
|
|
22
|
+
locales: Record<string, string>;
|
|
23
|
+
loadMessages: (locale: string) => Promise<Record<string, string>>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function I18nProvider({
|
|
27
|
+
children,
|
|
28
|
+
defaultLocale,
|
|
29
|
+
locales,
|
|
30
|
+
loadMessages,
|
|
31
|
+
}: I18nProviderProps) {
|
|
32
|
+
const [locale, setLocaleRaw] = useState(() => {
|
|
33
|
+
try {
|
|
34
|
+
const saved = localStorage.getItem("arc-locale");
|
|
35
|
+
if (saved && saved in locales) return saved;
|
|
36
|
+
} catch {}
|
|
37
|
+
return defaultLocale;
|
|
38
|
+
});
|
|
39
|
+
const [messages, setMessages] = useState<Record<string, string>>({});
|
|
40
|
+
|
|
41
|
+
const setLocale = useCallback(
|
|
42
|
+
async (newLocale: string) => {
|
|
43
|
+
const msgs = await loadMessages(newLocale);
|
|
44
|
+
setMessages(msgs);
|
|
45
|
+
setLocaleRaw(newLocale);
|
|
46
|
+
try { localStorage.setItem("arc-locale", newLocale); } catch {}
|
|
47
|
+
},
|
|
48
|
+
[loadMessages],
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
setLocale(locale);
|
|
53
|
+
}, []);
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<I18nContext.Provider value={{ locale, messages, setLocale, locales }}>
|
|
57
|
+
{children}
|
|
58
|
+
</I18nContext.Provider>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function useI18n() {
|
|
63
|
+
const ctx = useContext(I18nContext);
|
|
64
|
+
if (!ctx) throw new Error("useI18n must be used within I18nProvider");
|
|
65
|
+
return ctx;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function Trans({ children }: { children?: ReactNode }) {
|
|
69
|
+
const ctx = useContext(I18nContext);
|
|
70
|
+
if (!ctx || typeof children !== "string") return <>{children}</>;
|
|
71
|
+
return <>{ctx.messages[children] || children}</>;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function t(strings: TemplateStringsArray, ...values: any[]) {
|
|
75
|
+
return String.raw(strings, ...values);
|
|
76
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
// Arc Platform — public API
|
|
2
|
+
|
|
3
|
+
// Module system
|
|
4
|
+
export { module, page, wrapper, slot, contextElement, contextFragments } from "./arc";
|
|
5
|
+
/** @deprecated Use module() instead */
|
|
6
|
+
export { arc } from "./arc";
|
|
7
|
+
export {
|
|
8
|
+
clearModules,
|
|
9
|
+
clearRegistry,
|
|
10
|
+
getAllFragments,
|
|
11
|
+
getAllModules,
|
|
12
|
+
getContext,
|
|
13
|
+
getContextElementFragments,
|
|
14
|
+
getDefaultLayout,
|
|
15
|
+
getModule,
|
|
16
|
+
getPageByPath,
|
|
17
|
+
getPageFragments,
|
|
18
|
+
getSlotFragments,
|
|
19
|
+
getVariantOverrides,
|
|
20
|
+
getWrapperFragments,
|
|
21
|
+
registerModule,
|
|
22
|
+
setContext,
|
|
23
|
+
setDefaultLayout,
|
|
24
|
+
setVariantOverrides,
|
|
25
|
+
subscribe,
|
|
26
|
+
subscribeContext,
|
|
27
|
+
unregisterModule,
|
|
28
|
+
} from "./registry";
|
|
29
|
+
|
|
30
|
+
// Layout (re-exported from arc-ds)
|
|
31
|
+
export {
|
|
32
|
+
arcTransitions,
|
|
33
|
+
DragHandle,
|
|
34
|
+
DynamicSlotProvider,
|
|
35
|
+
ExpandablePanel,
|
|
36
|
+
Layout,
|
|
37
|
+
OverlayProvider,
|
|
38
|
+
ScrollNav,
|
|
39
|
+
SubNavShell,
|
|
40
|
+
ToolbarExpandProvider,
|
|
41
|
+
useExpandable,
|
|
42
|
+
useDynamicSlotContent,
|
|
43
|
+
useOverlay,
|
|
44
|
+
useSlotContent,
|
|
45
|
+
useToolbarExpand,
|
|
46
|
+
Z,
|
|
47
|
+
} from "@arcote.tech/arc-ds";
|
|
48
|
+
export type {
|
|
49
|
+
SubNavShellProps,
|
|
50
|
+
SubNavTab,
|
|
51
|
+
UseExpandableReturn,
|
|
52
|
+
} from "@arcote.tech/arc-ds";
|
|
53
|
+
|
|
54
|
+
// Layout (platform-specific — depends on registry/router)
|
|
55
|
+
export { BlankLayout } from "./layout/blank-layout";
|
|
56
|
+
export { ArcLayoutProvider } from "./layout/layout-provider";
|
|
57
|
+
export type { ArcLayoutProviderProps } from "./layout/layout-provider";
|
|
58
|
+
export { OverflowNav } from "./layout/overflow-nav";
|
|
59
|
+
export { PageRouter } from "./layout/page-router";
|
|
60
|
+
export { PageSubNavShell } from "./layout/page-sub-nav-shell";
|
|
61
|
+
export { SlotRenderer } from "./layout/slot-renderer";
|
|
62
|
+
export type { SlotRendererProps } from "./layout/slot-renderer";
|
|
63
|
+
export { useSlotFragments } from "./layout/use-slot-fragments";
|
|
64
|
+
|
|
65
|
+
// Router
|
|
66
|
+
export {
|
|
67
|
+
ArcRouterProvider,
|
|
68
|
+
useArcNavigate,
|
|
69
|
+
useArcRoute,
|
|
70
|
+
useArcParams,
|
|
71
|
+
useModuleNavigate,
|
|
72
|
+
} from "./router";
|
|
73
|
+
|
|
74
|
+
// Theme
|
|
75
|
+
export { ThemeProvider, useTheme } from "./theme";
|
|
76
|
+
|
|
77
|
+
// i18n
|
|
78
|
+
export { I18nProvider, Trans, t, useI18n } from "./i18n";
|
|
79
|
+
export type { I18nProviderProps } from "./i18n";
|
|
80
|
+
|
|
81
|
+
// Locale (Lingui) — deprecated, use I18nProvider
|
|
82
|
+
export { LocaleProvider, useLocale } from "./locale";
|
|
83
|
+
export type { LocaleProviderProps } from "./locale";
|
|
84
|
+
|
|
85
|
+
// Hooks
|
|
86
|
+
export { useTitle } from "./hooks/use-title";
|
|
87
|
+
|
|
88
|
+
// Platform
|
|
89
|
+
export { loadModules, reloadModules, useModuleLoader } from "./module-loader";
|
|
90
|
+
export type { ModuleLoaderState, ModuleManifest } from "./module-loader";
|
|
91
|
+
export { PlatformApp } from "./platform-app";
|
|
92
|
+
export type { PlatformAppProps } from "./platform-app";
|
|
93
|
+
export {
|
|
94
|
+
PlatformModelProvider,
|
|
95
|
+
usePlatformResetModel,
|
|
96
|
+
usePlatformScope,
|
|
97
|
+
} from "./platform-context";
|
|
98
|
+
|
|
99
|
+
// Types
|
|
100
|
+
export type {
|
|
101
|
+
ArcComponent,
|
|
102
|
+
ArcFactory,
|
|
103
|
+
ArcFactoryMethods,
|
|
104
|
+
ArcFragment,
|
|
105
|
+
ArcLayoutComponent,
|
|
106
|
+
ArcModule,
|
|
107
|
+
BuiltModule,
|
|
108
|
+
ContextElementFragment,
|
|
109
|
+
ExtractPages,
|
|
110
|
+
FragmentOrdering,
|
|
111
|
+
PageFragment,
|
|
112
|
+
PageOptions,
|
|
113
|
+
PageShellProps,
|
|
114
|
+
PublicArcFragment,
|
|
115
|
+
PublicPaths,
|
|
116
|
+
SlotFragment,
|
|
117
|
+
SlotId,
|
|
118
|
+
SlotOptions,
|
|
119
|
+
WrapperFragment,
|
|
120
|
+
WrapperOptions,
|
|
121
|
+
} from "./types";
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Blank layout — no chrome, no toolbar, just renders children.
|
|
5
|
+
* Use for pages that need full control: sign-in, onboarding, etc.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* import { BlankLayout } from "@arcote.tech/platform";
|
|
10
|
+
* page("/sign-in", SignInPage, { layout: BlankLayout })
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
export function BlankLayout({ children }: { children: ReactNode }) {
|
|
14
|
+
return <>{children}</>;
|
|
15
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ArcRouterProvider,
|
|
3
|
+
DynamicSlotProvider,
|
|
4
|
+
OverlayProvider,
|
|
5
|
+
SlotRenderProvider,
|
|
6
|
+
} from "@arcote.tech/arc-ds";
|
|
7
|
+
import type { SlotRenderFn } from "@arcote.tech/arc-ds";
|
|
8
|
+
import { AnimatePresence } from "framer-motion";
|
|
9
|
+
import { useCallback, type ReactNode } from "react";
|
|
10
|
+
import { SlotRenderer } from "./slot-renderer";
|
|
11
|
+
|
|
12
|
+
export interface ArcLayoutProviderProps {
|
|
13
|
+
children: ReactNode;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function ArcLayoutProvider({ children }: ArcLayoutProviderProps) {
|
|
17
|
+
const renderSlot: SlotRenderFn = useCallback(
|
|
18
|
+
(slotId, options) => (
|
|
19
|
+
<SlotRenderer
|
|
20
|
+
slotId={slotId}
|
|
21
|
+
displayMode={options?.displayMode as any}
|
|
22
|
+
className={options?.className}
|
|
23
|
+
/>
|
|
24
|
+
),
|
|
25
|
+
[],
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<ArcRouterProvider>
|
|
30
|
+
<SlotRenderProvider value={renderSlot}>
|
|
31
|
+
<OverlayProvider>
|
|
32
|
+
<DynamicSlotProvider>
|
|
33
|
+
<AnimatePresence mode="wait">{children}</AnimatePresence>
|
|
34
|
+
</DynamicSlotProvider>
|
|
35
|
+
</OverlayProvider>
|
|
36
|
+
</SlotRenderProvider>
|
|
37
|
+
</ArcRouterProvider>
|
|
38
|
+
);
|
|
39
|
+
}
|