akanjs 2.3.11-rc.9 → 2.3.11
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/CHANGELOG.md +21 -0
- package/client/csrTypes.ts +2 -0
- package/common/routeConvention.ts +8 -5
- package/package.json +1 -1
- package/server/routeTreeBuilder.ts +37 -4
- package/types/client/csrTypes.d.ts +2 -0
- package/types/common/routeConvention.d.ts +1 -1
- package/types/ui/Button.d.ts +6 -1
- package/types/ui/DatePicker.d.ts +12 -9
- package/types/ui/Dropdown.d.ts +6 -1
- package/types/ui/Empty.d.ts +7 -1
- package/types/ui/Input.d.ts +12 -8
- package/types/ui/Loading/index.d.ts +11 -6
- package/types/ui/Menu.d.ts +9 -4
- package/types/ui/Modal.d.ts +11 -1
- package/types/ui/Pagination.d.ts +6 -1
- package/types/ui/Popconfirm.d.ts +7 -1
- package/types/ui/Radio.d.ts +7 -4
- package/types/ui/Select.d.ts +6 -1
- package/types/ui/Table.d.ts +6 -1
- package/types/ui/ToggleSelect.d.ts +11 -7
- package/types/ui/UiOverride/Provider.d.ts +14 -0
- package/types/ui/UiOverride/context.d.ts +72 -0
- package/types/ui/UiOverride/createOverridable.d.ts +9 -0
- package/types/ui/UiOverride/index.d.ts +5 -0
- package/types/ui/UiOverride/override.d.ts +18 -0
- package/types/ui/UiOverride/useUiOverride.d.ts +7 -0
- package/types/ui/UiOverride.d.ts +1 -0
- package/types/ui/Unauthorized.d.ts +8 -3
- package/types/ui/index.d.ts +1 -0
- package/ui/Button.tsx +15 -2
- package/ui/DatePicker.tsx +19 -9
- package/ui/Dropdown.tsx +9 -1
- package/ui/Empty.tsx +11 -1
- package/ui/Input.tsx +22 -14
- package/ui/Loading/index.tsx +15 -1
- package/ui/Menu.tsx +12 -3
- package/ui/Modal.tsx +13 -1
- package/ui/Pagination.tsx +9 -1
- package/ui/Popconfirm.tsx +9 -1
- package/ui/Radio.tsx +14 -3
- package/ui/Select.tsx +22 -2
- package/ui/Table.tsx +8 -1
- package/ui/ToggleSelect.tsx +31 -5
- package/ui/UiOverride/Provider.tsx +22 -0
- package/ui/UiOverride/context.ts +85 -0
- package/ui/UiOverride/createOverridable.tsx +25 -0
- package/ui/UiOverride/index.ts +5 -0
- package/ui/UiOverride/override.ts +19 -0
- package/ui/UiOverride/useUiOverride.ts +15 -0
- package/ui/Unauthorized.tsx +12 -3
- package/ui/index.ts +10 -0
- package/webkit/bootCsr.tsx +28 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# akanjs
|
|
2
2
|
|
|
3
|
+
## 2.3.11
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 595390a: feat: UiOverride 시스템 및 \_overrides.tsx 지원 추가
|
|
8
|
+
|
|
9
|
+
- `akanjs/ui/UiOverride` 추가: `Provider`, `createOverridable`, `useUiOverride`, `override` API로 UI 컴포넌트 커스터마이징 지원
|
|
10
|
+
- 모든 akanjs UI 컴포넌트(Button, Modal, Select, Table 등)에 `useUiOverride()` 통합
|
|
11
|
+
- 라우트 시스템에 `_overrides.tsx` 지원 추가 (routeConvention, routeTreeBuilder)
|
|
12
|
+
- qualityScanner에 `_overrides.tsx` 파일 검증 로직 추가
|
|
13
|
+
- 앱 예제: `apps/minimal`에 `_overrides.tsx`, `BrandModal`, `OverrideDemo` 추가
|
|
14
|
+
- `apps/akan` 문서에 UI 커스터마이징 가이드 페이지 추가
|
|
15
|
+
- devkit에 `no-throw-raw-error.grit` lint rule 추가
|
|
16
|
+
- `PushNotificationServer.ts` 리팩토링
|
|
17
|
+
- biome.json 업데이트 및 패키지 의존성 정리
|
|
18
|
+
|
|
19
|
+
### Patch Changes
|
|
20
|
+
|
|
21
|
+
- 5ce752a: enhance: add host option for staging server tests
|
|
22
|
+
- 5ce752a: add host option for staging server tests
|
|
23
|
+
|
|
3
24
|
## 2.3.10
|
|
4
25
|
|
|
5
26
|
### Patch Changes
|
package/client/csrTypes.ts
CHANGED
|
@@ -191,6 +191,8 @@ export interface Route {
|
|
|
191
191
|
path: string;
|
|
192
192
|
renderPage?: RouteRender;
|
|
193
193
|
renderLayout?: RouteRender;
|
|
194
|
+
/** Synthetic layout render from a `_overrides.tsx` at this node; wraps the subtree in a UI-override provider. */
|
|
195
|
+
renderOverrides?: RouteRender;
|
|
194
196
|
pageIncludesOwnLayout?: boolean;
|
|
195
197
|
isSpecialRoute?: boolean;
|
|
196
198
|
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
const ROUTE_SOURCE_RE = /^\.\/(.+)\.(tsx|ts|jsx|js)$/;
|
|
2
2
|
const SOURCE_EXT_RE = /\.(tsx|ts|jsx|js)$/;
|
|
3
|
-
const RESERVED_ROUTE_FILES = new Set(["_layout", "_index"]);
|
|
3
|
+
const RESERVED_ROUTE_FILES = new Set(["_layout", "_index", "_overrides"]);
|
|
4
4
|
const INTERNAL_ROOT_LAYOUT_LEAF = "__root_layout";
|
|
5
5
|
const IMPLICIT_LOCALE_SEGMENT = "[lang]";
|
|
6
6
|
const SPECIAL_ROUTE_LEAVES = new Set(["robots.txt"]);
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
const DIRECTORY_SCOPED_LEAVES = new Set(["_layout", "_index", "_overrides"]);
|
|
9
|
+
|
|
10
|
+
export type RouteModuleKind = "page" | "layout" | "overrides";
|
|
9
11
|
|
|
10
12
|
export interface ParsedRouteModuleKey {
|
|
11
13
|
key: string;
|
|
@@ -51,7 +53,7 @@ export function validatePageSourceFile(filePath: string, options: ValidatePageSo
|
|
|
51
53
|
if (ext !== "tsx") throw new Error(`[route-convention] route source files under page/ must use .tsx: ${displayPath}`);
|
|
52
54
|
if (leaf.startsWith("_") && !RESERVED_ROUTE_FILES.has(leaf) && leaf !== INTERNAL_ROOT_LAYOUT_LEAF)
|
|
53
55
|
throw new Error(
|
|
54
|
-
`[route-convention] only _index.tsx and
|
|
56
|
+
`[route-convention] only _index.tsx, _layout.tsx and _overrides.tsx are allowed as reserved route files under page/: ${displayPath}`,
|
|
55
57
|
);
|
|
56
58
|
if (/^[A-Z]/.test(leaf))
|
|
57
59
|
throw new Error(`[route-convention] route page filenames must not start with an uppercase letter: ${displayPath}`);
|
|
@@ -97,13 +99,14 @@ export function parseRouteModuleKey(key: string): ParsedRouteModuleKey {
|
|
|
97
99
|
}
|
|
98
100
|
|
|
99
101
|
const isInternalRootLayout = leaf === INTERNAL_ROOT_LAYOUT_LEAF;
|
|
100
|
-
const kind: RouteModuleKind =
|
|
102
|
+
const kind: RouteModuleKind =
|
|
103
|
+
leaf === "_layout" || isInternalRootLayout ? "layout" : leaf === "_overrides" ? "overrides" : "page";
|
|
101
104
|
if (leaf.startsWith("_") && !RESERVED_ROUTE_FILES.has(leaf) && !isInternalRootLayout) {
|
|
102
105
|
throw new Error(`[route-convention] unsupported reserved route file "${leaf}" in ${key}`);
|
|
103
106
|
}
|
|
104
107
|
|
|
105
108
|
const sourceRouteSegments =
|
|
106
|
-
leaf
|
|
109
|
+
DIRECTORY_SCOPED_LEAVES.has(leaf) || isInternalRootLayout ? moduleSegments.slice(0, -1) : moduleSegments;
|
|
107
110
|
const isSpecialRoute = kind === "page" && SPECIAL_ROUTE_LEAVES.has(leaf);
|
|
108
111
|
const routeSegments = isSpecialRoute ? sourceRouteSegments : [IMPLICIT_LOCALE_SEGMENT, ...sourceRouteSegments];
|
|
109
112
|
for (const segment of routeSegments) validateRouteSegment(segment, key);
|
package/package.json
CHANGED
|
@@ -18,9 +18,12 @@ import {
|
|
|
18
18
|
parseRouteModuleKey,
|
|
19
19
|
routeSegmentToTreePath,
|
|
20
20
|
} from "akanjs/common";
|
|
21
|
+
import { createElement } from "react";
|
|
21
22
|
import { validatePageConfig } from "../client/frameConfig";
|
|
22
23
|
import { resolveHeadExport, resolveMetadataHead } from "./metadata";
|
|
23
24
|
|
|
25
|
+
type RouteModuleKindWithOverrides = "page" | "layout" | "overrides";
|
|
26
|
+
|
|
24
27
|
export type PagesContext = Record<string, () => Promise<RouteModule>>;
|
|
25
28
|
|
|
26
29
|
export const defaultPageState: PageState = {
|
|
@@ -197,6 +200,14 @@ export class RouteTreeBuilder {
|
|
|
197
200
|
const targetPath = pathSegments[pathSegments.length - 1];
|
|
198
201
|
if (!targetPath) return;
|
|
199
202
|
|
|
203
|
+
if (parsed.kind === "overrides") {
|
|
204
|
+
targetRouteMap.set(targetPath, {
|
|
205
|
+
...(targetRouteMap.get(targetPath) ?? { path: targetPath, children: new Map<string, Route>() }),
|
|
206
|
+
renderOverrides: this.#makeOverridesRender(filePath, loader),
|
|
207
|
+
} as Route);
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
|
|
200
211
|
const routeRender = RouteTreeBuilder.#makeRouteRender(filePath, parsed.kind, loader);
|
|
201
212
|
targetRouteMap.set(targetPath, {
|
|
202
213
|
...(targetRouteMap.get(targetPath) ?? { path: targetPath, children: new Map<string, Route>() }),
|
|
@@ -224,8 +235,10 @@ export class RouteTreeBuilder {
|
|
|
224
235
|
const pathSegments = [...parentPaths, ...(currentPathSegment ? [currentPathSegment] : [])];
|
|
225
236
|
const currentRootLayout = isRoot && route.renderLayout ? route.renderLayout : null;
|
|
226
237
|
const currentLayout = !isRoot && route.renderLayout ? route.renderLayout : null;
|
|
238
|
+
|
|
239
|
+
const currentOverrideRenders = route.renderOverrides ? [route.renderOverrides] : [];
|
|
227
240
|
const renderRootLayouts = [...parentRootLayouts, ...(currentRootLayout ? [currentRootLayout] : [])];
|
|
228
|
-
const renderLayouts = [...parentLayouts, ...(currentLayout ? [currentLayout] : [])];
|
|
241
|
+
const renderLayouts = [...parentLayouts, ...currentOverrideRenders, ...(currentLayout ? [currentLayout] : [])];
|
|
229
242
|
if (route.renderLayout) {
|
|
230
243
|
this.#fallbackRoutes.push({
|
|
231
244
|
path: routePath,
|
|
@@ -237,7 +250,10 @@ export class RouteTreeBuilder {
|
|
|
237
250
|
const routeHead = RouteTreeBuilder.#composeHeadResolvers(route.renderLayout?.resolveHead, parentHead);
|
|
238
251
|
const pageRenderRootLayouts =
|
|
239
252
|
route.pageIncludesOwnLayout === false && currentRootLayout ? parentRootLayouts : renderRootLayouts;
|
|
240
|
-
const pageRenderLayouts =
|
|
253
|
+
const pageRenderLayouts =
|
|
254
|
+
route.pageIncludesOwnLayout === false && currentLayout
|
|
255
|
+
? [...parentLayouts, ...currentOverrideRenders]
|
|
256
|
+
: renderLayouts;
|
|
241
257
|
const pageHead = route.pageIncludesOwnLayout === false ? parentHead : routeHead;
|
|
242
258
|
return [
|
|
243
259
|
...(route.renderPage
|
|
@@ -262,7 +278,7 @@ export class RouteTreeBuilder {
|
|
|
262
278
|
];
|
|
263
279
|
}
|
|
264
280
|
|
|
265
|
-
static #makeLazyModule(key: string, kind:
|
|
281
|
+
static #makeLazyModule(key: string, kind: RouteModuleKindWithOverrides, loader: () => Promise<RouteModule>) {
|
|
266
282
|
let cached: RouteModule | null = null;
|
|
267
283
|
let loaded = false;
|
|
268
284
|
RouteTreeBuilder.#moduleCacheStats.moduleCount += 1;
|
|
@@ -285,7 +301,12 @@ export class RouteTreeBuilder {
|
|
|
285
301
|
};
|
|
286
302
|
}
|
|
287
303
|
|
|
288
|
-
static #validateRouteModuleExports(key: string, kind:
|
|
304
|
+
static #validateRouteModuleExports(key: string, kind: RouteModuleKindWithOverrides, mod: RouteModule) {
|
|
305
|
+
if (kind === "overrides") {
|
|
306
|
+
|
|
307
|
+
if (!mod.default) throw new Error(`[route-convention] ${key} generated override wrapper has no default export`);
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
289
310
|
const parsed = parseRouteModuleKey(key);
|
|
290
311
|
const allowed =
|
|
291
312
|
kind === "page"
|
|
@@ -378,6 +399,18 @@ export class RouteTreeBuilder {
|
|
|
378
399
|
return routeRender;
|
|
379
400
|
}
|
|
380
401
|
|
|
402
|
+
#makeOverridesRender(key: string, loader: () => Promise<RouteModule>): RouteRender {
|
|
403
|
+
const loadModule = RouteTreeBuilder.#makeLazyModule(key, "overrides", loader);
|
|
404
|
+
return {
|
|
405
|
+
isAsync: true,
|
|
406
|
+
render: (async ({ children }: LayoutProps) => {
|
|
407
|
+
const mod = await loadModule();
|
|
408
|
+
if (!mod.default) throw new Error(`[route-convention] ${key} generated override wrapper has no default export`);
|
|
409
|
+
return createElement(mod.default as never, { children } as never);
|
|
410
|
+
}) as RouteRender["render"],
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
|
|
381
414
|
static #composeHeadResolvers(...resolvers: (ResolveHead | undefined)[]): ResolveHead | undefined {
|
|
382
415
|
const chain = resolvers.filter((resolver): resolver is ResolveHead => Boolean(resolver));
|
|
383
416
|
if (chain.length === 0) return undefined;
|
|
@@ -190,6 +190,8 @@ export interface Route {
|
|
|
190
190
|
path: string;
|
|
191
191
|
renderPage?: RouteRender;
|
|
192
192
|
renderLayout?: RouteRender;
|
|
193
|
+
/** Synthetic layout render from a `_overrides.tsx` at this node; wraps the subtree in a UI-override provider. */
|
|
194
|
+
renderOverrides?: RouteRender;
|
|
193
195
|
pageIncludesOwnLayout?: boolean;
|
|
194
196
|
isSpecialRoute?: boolean;
|
|
195
197
|
loader?: () => unknown;
|
package/types/ui/Button.d.ts
CHANGED
|
@@ -8,4 +8,9 @@ export type ButtonProps<Result> = Omit<ButtonHTMLAttributes<HTMLButtonElement>,
|
|
|
8
8
|
/** Called after the button briefly enters success state. */
|
|
9
9
|
onSuccess?: (result: Result) => void;
|
|
10
10
|
};
|
|
11
|
-
|
|
11
|
+
/**
|
|
12
|
+
* Async-aware button. Resolves to a route-scoped override when a `page/**\/_overrides.tsx`
|
|
13
|
+
* in the route's ancestry declares one, otherwise renders {@link DefaultButton}. The public
|
|
14
|
+
* generic signature is preserved, so `<Button<Todo> onSuccess={(r: Todo) => …} />` still infers.
|
|
15
|
+
*/
|
|
16
|
+
export declare const Button: <Result = unknown>(props: ButtonProps<Result>) => React.ReactElement<ButtonProps<Result>, string | React.JSXElementConstructor<any>>;
|
package/types/ui/DatePicker.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type Dayjs } from "akanjs/base";
|
|
2
|
-
interface DatePickerProps {
|
|
2
|
+
export interface DatePickerProps {
|
|
3
3
|
value?: Dayjs | null;
|
|
4
4
|
onChange: (value: Dayjs | null) => void;
|
|
5
5
|
showTime?: boolean;
|
|
@@ -10,12 +10,7 @@ interface DatePickerProps {
|
|
|
10
10
|
placement?: "top" | "bottom" | "left" | "right";
|
|
11
11
|
defaultValue?: Dayjs;
|
|
12
12
|
}
|
|
13
|
-
export
|
|
14
|
-
({ value, onChange, showTime, format, timeIntervals, disabledDate, placement, className, defaultValue, }: DatePickerProps): import("react/jsx-runtime").JSX.Element;
|
|
15
|
-
RangePicker: ({ value, onChange, format, showTime, timeIntervals, disabledDate, className, }: RangePickerProps) => import("react/jsx-runtime").JSX.Element;
|
|
16
|
-
TimePicker: ({ disabled, className, value, format, onChange, timeIntervals, }: TimePickerProps) => import("react/jsx-runtime").JSX.Element;
|
|
17
|
-
};
|
|
18
|
-
interface RangePickerProps {
|
|
13
|
+
export interface RangePickerProps {
|
|
19
14
|
value: [Dayjs | null, Dayjs | null];
|
|
20
15
|
onChange: (value: [Dayjs | null, Dayjs | null]) => void;
|
|
21
16
|
format?: string;
|
|
@@ -24,7 +19,7 @@ interface RangePickerProps {
|
|
|
24
19
|
disabledDate?: (date: Dayjs) => boolean | null | undefined;
|
|
25
20
|
className?: string;
|
|
26
21
|
}
|
|
27
|
-
interface TimePickerProps {
|
|
22
|
+
export interface TimePickerProps {
|
|
28
23
|
value: Dayjs | null;
|
|
29
24
|
onChange: (value: Dayjs) => void;
|
|
30
25
|
format?: string;
|
|
@@ -33,4 +28,12 @@ interface TimePickerProps {
|
|
|
33
28
|
className?: string;
|
|
34
29
|
disabled?: boolean;
|
|
35
30
|
}
|
|
36
|
-
|
|
31
|
+
/**
|
|
32
|
+
* Date picker. `DatePicker`, `DatePicker.RangePicker`, and `DatePicker.TimePicker` each resolve to a
|
|
33
|
+
* route-scoped override when a `page/**\/_overrides.tsx` in the route's ancestry declares one (slots
|
|
34
|
+
* `DatePicker`, `DatePickerRangePicker`, `DatePickerTimePicker`).
|
|
35
|
+
*/
|
|
36
|
+
export declare const DatePicker: import("react").ComponentType<DatePickerProps> & {
|
|
37
|
+
RangePicker: import("react").ComponentType<RangePickerProps>;
|
|
38
|
+
TimePicker: import("react").ComponentType<TimePickerProps>;
|
|
39
|
+
};
|
package/types/ui/Dropdown.d.ts
CHANGED
|
@@ -11,4 +11,9 @@ export interface DropdownProps {
|
|
|
11
11
|
/** Additional classes for the dropdown content panel. */
|
|
12
12
|
dropdownClassName?: string;
|
|
13
13
|
}
|
|
14
|
-
export declare const
|
|
14
|
+
export declare const DefaultDropdown: ({ value, content, className, buttonClassName, dropdownClassName }: DropdownProps) => import("react/jsx-runtime").JSX.Element;
|
|
15
|
+
/**
|
|
16
|
+
* Dropdown. Resolves to a route-scoped override when a `page/**\/_overrides.tsx`
|
|
17
|
+
* in the route's ancestry declares one, otherwise renders {@link DefaultDropdown}.
|
|
18
|
+
*/
|
|
19
|
+
export declare const Dropdown: import("react").ComponentType<DropdownProps>;
|
package/types/ui/Empty.d.ts
CHANGED
|
@@ -9,4 +9,10 @@ export interface EmptyProps {
|
|
|
9
9
|
/** Minimum empty-state height in pixels. */
|
|
10
10
|
minHeight?: number;
|
|
11
11
|
}
|
|
12
|
-
export declare const
|
|
12
|
+
export declare const DefaultEmpty: ({ className, description, children, minHeight }: EmptyProps) => import("react/jsx-runtime").JSX.Element;
|
|
13
|
+
/**
|
|
14
|
+
* Empty-state placeholder. Resolves to a route-scoped override when a
|
|
15
|
+
* `page/**\/_overrides.tsx` in the route's ancestry declares one, otherwise
|
|
16
|
+
* renders {@link DefaultEmpty}.
|
|
17
|
+
*/
|
|
18
|
+
export declare const Empty: import("react").ComponentType<EmptyProps>;
|
package/types/ui/Input.d.ts
CHANGED
|
@@ -24,14 +24,6 @@ export type InputProps = Omit<InputHTMLAttributes<HTMLInputElement>, "value" | "
|
|
|
24
24
|
/** Called when Escape is pressed after blurring the input. */
|
|
25
25
|
onPressEscape?: (e: KeyboardEvent<HTMLInputElement>) => void;
|
|
26
26
|
};
|
|
27
|
-
export declare const Input: {
|
|
28
|
-
({ className, nullable, inputRef, value, cacheKey, inputStyleType, icon, iconClassName, inputClassName, inputWrapperClassName, onPressEnter, onPressEscape, onChange, validate, ...rest }: InputProps): import("react/jsx-runtime").JSX.Element;
|
|
29
|
-
TextArea: ({ className, nullable, value, inputClassName, inputWrapperClassName, cacheKey, onPressEnter, onPressEscape, onChange, validate, ...rest }: TextAreaProps) => import("react/jsx-runtime").JSX.Element;
|
|
30
|
-
Password: ({ className, nullable, value, icon, iconClassName, inputClassName, inputWrapperClassName, cacheKey, onPressEnter, onPressEscape, onChange, validate, ...rest }: PasswordProps) => import("react/jsx-runtime").JSX.Element;
|
|
31
|
-
Email: ({ inputStyleType, className, nullable, value, cacheKey, onPressEnter, onPressEscape, onChange, validate, icon, iconClassName, inputClassName, inputWrapperClassName, ...rest }: EmailProps) => import("react/jsx-runtime").JSX.Element;
|
|
32
|
-
Number: ({ className, nullable, value, icon, cacheKey, iconClassName, inputClassName, inputWrapperClassName, numberFormat, onPressEnter, onPressEscape, validate, onChange, formatter, parser, ...rest }: NumberProps) => import("react/jsx-runtime").JSX.Element;
|
|
33
|
-
Checkbox: ({ checked, onChange, className, ...rest }: CheckboxProps) => import("react/jsx-runtime").JSX.Element;
|
|
34
|
-
};
|
|
35
27
|
export type TextAreaProps = Omit<TextareaHTMLAttributes<HTMLTextAreaElement>, "value" | "type" | "onChange" | "onPressEnter"> & {
|
|
36
28
|
inputRef?: RefObject<HTMLTextAreaElement | null>;
|
|
37
29
|
value: string;
|
|
@@ -93,3 +85,15 @@ export type CheckboxProps = Omit<InputHTMLAttributes<HTMLInputElement>, "onChang
|
|
|
93
85
|
checked: boolean;
|
|
94
86
|
onChange: (checked: boolean, e: ChangeEvent<HTMLInputElement>) => void;
|
|
95
87
|
};
|
|
88
|
+
/**
|
|
89
|
+
* Text input plus its field variants. Each leaf resolves to a route-scoped override when a
|
|
90
|
+
* `page/**\/_overrides.tsx` in the route's ancestry declares one (slots `Input`, `InputTextArea`,
|
|
91
|
+
* `InputPassword`, `InputEmail`, `InputNumber`, `InputCheckbox`), otherwise renders the default.
|
|
92
|
+
*/
|
|
93
|
+
export declare const Input: React.ComponentType<InputProps> & {
|
|
94
|
+
TextArea: React.ComponentType<TextAreaProps>;
|
|
95
|
+
Password: React.ComponentType<PasswordProps>;
|
|
96
|
+
Email: React.ComponentType<EmailProps>;
|
|
97
|
+
Number: React.ComponentType<NumberProps>;
|
|
98
|
+
Checkbox: React.ComponentType<CheckboxProps>;
|
|
99
|
+
};
|
|
@@ -1,8 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loading indicators. Each member is an independent override slot (`LoadingSpin`, `LoadingSkeleton`,
|
|
3
|
+
* `LoadingProgressBar`, `LoadingButton`, `LoadingInput`, `LoadingArea`), resolved from the closest
|
|
4
|
+
* `page/**\/_overrides.tsx` in the route's ancestry, otherwise the shipped default.
|
|
5
|
+
*/
|
|
1
6
|
export declare const Loading: {
|
|
2
|
-
Area:
|
|
3
|
-
Button: (
|
|
4
|
-
Input: (
|
|
5
|
-
ProgressBar: (
|
|
6
|
-
Skeleton: (
|
|
7
|
-
Spin: (
|
|
7
|
+
Area: import("react").ComponentType<Record<string, never>>;
|
|
8
|
+
Button: import("react").ComponentType<import("./Button.d.ts").LoadingProps>;
|
|
9
|
+
Input: import("react").ComponentType<import("./Input.d.ts").LoadingProps>;
|
|
10
|
+
ProgressBar: import("react").ComponentType<import("./ProgressBar.d.ts").ProgressBarProps>;
|
|
11
|
+
Skeleton: import("react").ComponentType<import("./Skeleton.d.ts").SkeletonProps>;
|
|
12
|
+
Spin: import("react").ComponentType<import("./Spin.d.ts").SpinProps>;
|
|
8
13
|
};
|
package/types/ui/Menu.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { type ReactNode } from "react";
|
|
2
|
-
interface MenuItem {
|
|
2
|
+
export interface MenuItem {
|
|
3
3
|
label: ReactNode;
|
|
4
4
|
key: string;
|
|
5
5
|
children?: MenuItem[];
|
|
6
6
|
icon?: ReactNode;
|
|
7
7
|
type?: string;
|
|
8
8
|
}
|
|
9
|
-
interface MenuProps {
|
|
9
|
+
export interface MenuProps {
|
|
10
10
|
className?: string;
|
|
11
11
|
ulClassName?: string;
|
|
12
12
|
liClassName?: string;
|
|
@@ -22,5 +22,10 @@ interface MenuProps {
|
|
|
22
22
|
onMouseOver?: () => void;
|
|
23
23
|
onMouseLeave?: () => void;
|
|
24
24
|
}
|
|
25
|
-
export declare const
|
|
26
|
-
|
|
25
|
+
export declare const DefaultMenu: ({ items, onClick, selectedKeys, labelClassName, defaultSelectedKeys, className, ulClassName, liClassName, style, mode, activeStyle, inlineCollapsed, onMouseOver, onMouseLeave, }: MenuProps) => import("react/jsx-runtime").JSX.Element;
|
|
26
|
+
/**
|
|
27
|
+
* Navigation menu. Resolves to a route-scoped override when a
|
|
28
|
+
* `page/**\/_overrides.tsx` in the route's ancestry declares one, otherwise
|
|
29
|
+
* renders {@link DefaultMenu}.
|
|
30
|
+
*/
|
|
31
|
+
export declare const Menu: import("react").ComponentType<MenuProps>;
|
package/types/ui/Modal.d.ts
CHANGED
|
@@ -16,4 +16,14 @@ export interface ModalProps {
|
|
|
16
16
|
/** Ask for close confirmation before dismissing. */
|
|
17
17
|
confirmClose?: boolean;
|
|
18
18
|
}
|
|
19
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Default akanjs modal skin. Kept as the fallback implementation; apps replace
|
|
21
|
+
* it per-route through a `page/**\/_overrides.tsx` manifest.
|
|
22
|
+
*/
|
|
23
|
+
export declare const DefaultModal: ({ className, title, action, open, onCancel, bodyClassName, children, confirmClose, }: ModalProps) => import("react/jsx-runtime").JSX.Element;
|
|
24
|
+
/**
|
|
25
|
+
* Public Modal. Resolves to a route-scoped override when a `_overrides.tsx` in
|
|
26
|
+
* the route's ancestry declares one, otherwise renders {@link DefaultModal}.
|
|
27
|
+
* The proxy is transparent to every existing `<Modal … />` call site.
|
|
28
|
+
*/
|
|
29
|
+
export declare const Modal: import("react").ComponentType<ModalProps>;
|
package/types/ui/Pagination.d.ts
CHANGED
|
@@ -17,4 +17,9 @@ export interface PaginationProps {
|
|
|
17
17
|
pageNumClassName?: string;
|
|
18
18
|
};
|
|
19
19
|
}
|
|
20
|
-
export declare const
|
|
20
|
+
export declare const DefaultPagination: FC<PaginationProps>;
|
|
21
|
+
/**
|
|
22
|
+
* Pager. Resolves to a route-scoped override when a `page/**\/_overrides.tsx` in
|
|
23
|
+
* the route's ancestry declares one, otherwise renders {@link DefaultPagination}.
|
|
24
|
+
*/
|
|
25
|
+
export declare const Pagination: import("react").ComponentType<PaginationProps>;
|
package/types/ui/Popconfirm.d.ts
CHANGED
|
@@ -24,5 +24,11 @@ export interface PopconfirmProps {
|
|
|
24
24
|
/** Additional classes for the popover arrow/decorator. */
|
|
25
25
|
decoClassName?: string;
|
|
26
26
|
}
|
|
27
|
-
export declare const
|
|
27
|
+
export declare const DefaultPopconfirm: ({ title, description, onConfirm, okButtonProps, cancelButtonProps, okText, cancelText, children, triggerClassName, decoClassName, }: PopconfirmProps) => import("react/jsx-runtime").JSX.Element;
|
|
28
|
+
/**
|
|
29
|
+
* Confirmation popover. Resolves to a route-scoped override when a
|
|
30
|
+
* `page/**\/_overrides.tsx` in the route's ancestry declares one, otherwise
|
|
31
|
+
* renders {@link DefaultPopconfirm}.
|
|
32
|
+
*/
|
|
33
|
+
export declare const Popconfirm: import("react").ComponentType<PopconfirmProps>;
|
|
28
34
|
export {};
|
package/types/ui/Radio.d.ts
CHANGED
|
@@ -6,10 +6,6 @@ export interface RadioProps {
|
|
|
6
6
|
children: ReactNode | ReactElement | ReactElement[];
|
|
7
7
|
onChange: (value: string | number | null, idx: number) => void;
|
|
8
8
|
}
|
|
9
|
-
export declare const Radio: {
|
|
10
|
-
({ value, children, disabled, className, onChange }: RadioProps): import("react/jsx-runtime").JSX.Element;
|
|
11
|
-
Item: ({ value, className, children }: ItemProps) => import("react/jsx-runtime").JSX.Element;
|
|
12
|
-
};
|
|
13
9
|
export interface ItemProps {
|
|
14
10
|
value: string | number;
|
|
15
11
|
children: ReactNode | ReactElement;
|
|
@@ -17,3 +13,10 @@ export interface ItemProps {
|
|
|
17
13
|
checked?: boolean;
|
|
18
14
|
onChange?: (value: string) => void;
|
|
19
15
|
}
|
|
16
|
+
/**
|
|
17
|
+
* Radio group. `Radio` and `Radio.Item` each resolve to a route-scoped override when a
|
|
18
|
+
* `page/**\/_overrides.tsx` in the route's ancestry declares one (slots `Radio`, `RadioItem`).
|
|
19
|
+
*/
|
|
20
|
+
export declare const Radio: import("react").ComponentType<RadioProps> & {
|
|
21
|
+
Item: import("react").ComponentType<ItemProps>;
|
|
22
|
+
};
|
package/types/ui/Select.d.ts
CHANGED
|
@@ -39,5 +39,10 @@ export interface SelectProps<T extends string | number | boolean | null | undefi
|
|
|
39
39
|
/** Custom selected value renderer. */
|
|
40
40
|
renderSelected?: (value: T) => ReactNode;
|
|
41
41
|
}
|
|
42
|
-
|
|
42
|
+
/**
|
|
43
|
+
* Select. Resolves to a route-scoped override when a `page/**\/_overrides.tsx` in the route's
|
|
44
|
+
* ancestry declares one, otherwise renders {@link DefaultSelect}. The public generic signature is
|
|
45
|
+
* preserved, so `<Select<MyEnum, true> …/>` still infers the value/onChange shape.
|
|
46
|
+
*/
|
|
47
|
+
export declare const Select: <T extends string | number | boolean | null | undefined, Multiple extends boolean = false, Searchable extends boolean = false, Option extends Options<T> = Options<T>>(props: SelectProps<T, Multiple, Searchable, Option>) => import("react").ReactElement<SelectProps<T, Multiple, Searchable, Option>, string | import("react").JSXElementConstructor<any>>;
|
|
43
48
|
export {};
|
package/types/ui/Table.d.ts
CHANGED
|
@@ -38,4 +38,9 @@ export interface TableProps {
|
|
|
38
38
|
/** Custom row key resolver. */
|
|
39
39
|
rowKey?: (model: any) => string;
|
|
40
40
|
}
|
|
41
|
-
export declare const
|
|
41
|
+
export declare const DefaultTable: ({ columns, dataSource, loading, size, bordered, pagination, showHeader, onRow, rowClassName, rowKey, }: TableProps) => import("react/jsx-runtime").JSX.Element;
|
|
42
|
+
/**
|
|
43
|
+
* Data table. Resolves to a route-scoped override when a `page/**\/_overrides.tsx`
|
|
44
|
+
* in the route's ancestry declares one, otherwise renders {@link DefaultTable}.
|
|
45
|
+
*/
|
|
46
|
+
export declare const Table: React.ComponentType<TableProps>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
import { type ComponentType } from "react";
|
|
2
|
+
export interface ToggleSelectProps<I extends string | number | boolean | null> {
|
|
2
3
|
className?: string;
|
|
3
4
|
btnClassName?: string;
|
|
4
5
|
items: string[] | number[] | {
|
|
@@ -12,11 +13,7 @@ interface ToggleSelectProps<I extends string | number | boolean | null> {
|
|
|
12
13
|
onChange: (value: I, idx: number) => void;
|
|
13
14
|
disabled?: boolean;
|
|
14
15
|
}
|
|
15
|
-
export
|
|
16
|
-
<I extends string | number | boolean | null>({ className, btnClassName, items, nullable, validate, value, onChange, disabled, }: ToggleSelectProps<I>): import("react/jsx-runtime").JSX.Element;
|
|
17
|
-
Multi: ({ className, btnClassName, items, nullable, validate, value, onChange, disabled }: MultiProps) => import("react/jsx-runtime").JSX.Element;
|
|
18
|
-
};
|
|
19
|
-
interface MultiProps {
|
|
16
|
+
export interface MultiProps {
|
|
20
17
|
className?: string;
|
|
21
18
|
btnClassName?: string;
|
|
22
19
|
items: string[] | number[] | {
|
|
@@ -30,4 +27,11 @@ interface MultiProps {
|
|
|
30
27
|
onChange: (value: string[] | number[]) => void;
|
|
31
28
|
disabled?: boolean;
|
|
32
29
|
}
|
|
33
|
-
|
|
30
|
+
/**
|
|
31
|
+
* Toggle-select. `ToggleSelect` keeps its generic signature (so `<ToggleSelect<Status> …/>` still
|
|
32
|
+
* infers), and both it and `ToggleSelect.Multi` resolve to a route-scoped override when a
|
|
33
|
+
* `page/**\/_overrides.tsx` in the route's ancestry declares one (slots `ToggleSelect`, `ToggleSelectMulti`).
|
|
34
|
+
*/
|
|
35
|
+
export declare const ToggleSelect: (<I extends string | number | boolean | null>(props: ToggleSelectProps<I>) => import("react").ReactElement<ToggleSelectProps<I>, string | import("react").JSXElementConstructor<any>>) & {
|
|
36
|
+
Multi: ComponentType<MultiProps>;
|
|
37
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import { type AkanUiOverrides } from "./context.d.ts";
|
|
3
|
+
export interface UiOverrideProviderProps {
|
|
4
|
+
/** Override map for this subtree; merged over any ancestor overrides. */
|
|
5
|
+
value?: Partial<AkanUiOverrides>;
|
|
6
|
+
children?: ReactNode;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Supplies route-scoped UI overrides. Merges its own `value` over the overrides
|
|
10
|
+
* inherited from ancestors so the closest declaration wins, matching nested
|
|
11
|
+
* `_overrides.tsx` resolution. `routeTreeBuilder` mounts one of these per route
|
|
12
|
+
* node once the `_overrides.tsx` convention is wired.
|
|
13
|
+
*/
|
|
14
|
+
export declare const UiOverrideProvider: ({ value, children }: UiOverrideProviderProps) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { type ComponentType } from "react";
|
|
2
|
+
import type { ButtonProps } from "../Button.d.ts";
|
|
3
|
+
import type { DatePickerProps, RangePickerProps, TimePickerProps } from "../DatePicker.d.ts";
|
|
4
|
+
import type { DropdownProps } from "../Dropdown.d.ts";
|
|
5
|
+
import type { EmptyProps } from "../Empty.d.ts";
|
|
6
|
+
import type { CheckboxProps, EmailProps, InputProps, NumberProps, PasswordProps, TextAreaProps } from "../Input.d.ts";
|
|
7
|
+
import type { LoadingProps as LoadingButtonProps } from "../Loading/Button.d.ts";
|
|
8
|
+
import type { LoadingProps as LoadingInputProps } from "../Loading/Input.d.ts";
|
|
9
|
+
import type { ProgressBarProps } from "../Loading/ProgressBar.d.ts";
|
|
10
|
+
import type { SkeletonProps } from "../Loading/Skeleton.d.ts";
|
|
11
|
+
import type { SpinProps } from "../Loading/Spin.d.ts";
|
|
12
|
+
import type { MenuProps } from "../Menu.d.ts";
|
|
13
|
+
import type { ModalProps } from "../Modal.d.ts";
|
|
14
|
+
import type { PaginationProps } from "../Pagination.d.ts";
|
|
15
|
+
import type { PopconfirmProps } from "../Popconfirm.d.ts";
|
|
16
|
+
import type { ItemProps as RadioItemProps, RadioProps } from "../Radio.d.ts";
|
|
17
|
+
import type { SelectProps } from "../Select.d.ts";
|
|
18
|
+
import type { TableProps } from "../Table.d.ts";
|
|
19
|
+
import type { MultiProps as ToggleSelectMultiProps, ToggleSelectProps } from "../ToggleSelect.d.ts";
|
|
20
|
+
import type { UnauthorizedProps } from "../Unauthorized.d.ts";
|
|
21
|
+
/**
|
|
22
|
+
* Registry of framework UI components an app may replace per-route through a
|
|
23
|
+
* `page/**\/_overrides.tsx` manifest. Each entry is keyed by the framework
|
|
24
|
+
* component name and typed to that component's public prop contract, so an
|
|
25
|
+
* override is checked as a drop-in replacement.
|
|
26
|
+
*
|
|
27
|
+
* Extend this interface (and add a matching `createOverridable(...)` call in the
|
|
28
|
+
* component file) to make another component overridable. Generic components
|
|
29
|
+
* (e.g. `Button`, `Select`) and compound components that carry static
|
|
30
|
+
* sub-components (e.g. `Tab`, `Field`, `Input`) need dedicated slot support
|
|
31
|
+
* before they can be listed here without losing call-site typing.
|
|
32
|
+
*/
|
|
33
|
+
export interface AkanUiOverrides {
|
|
34
|
+
Modal: ComponentType<ModalProps>;
|
|
35
|
+
Empty: ComponentType<EmptyProps>;
|
|
36
|
+
Pagination: ComponentType<PaginationProps>;
|
|
37
|
+
Popconfirm: ComponentType<PopconfirmProps>;
|
|
38
|
+
Dropdown: ComponentType<DropdownProps>;
|
|
39
|
+
Table: ComponentType<TableProps>;
|
|
40
|
+
Menu: ComponentType<MenuProps>;
|
|
41
|
+
Unauthorized: ComponentType<UnauthorizedProps>;
|
|
42
|
+
Button: ComponentType<ButtonProps<unknown>>;
|
|
43
|
+
Select: ComponentType<SelectProps<string | number | boolean | null | undefined>>;
|
|
44
|
+
Input: ComponentType<InputProps>;
|
|
45
|
+
InputTextArea: ComponentType<TextAreaProps>;
|
|
46
|
+
InputPassword: ComponentType<PasswordProps>;
|
|
47
|
+
InputEmail: ComponentType<EmailProps>;
|
|
48
|
+
InputNumber: ComponentType<NumberProps>;
|
|
49
|
+
InputCheckbox: ComponentType<CheckboxProps>;
|
|
50
|
+
Radio: ComponentType<RadioProps>;
|
|
51
|
+
RadioItem: ComponentType<RadioItemProps>;
|
|
52
|
+
DatePicker: ComponentType<DatePickerProps>;
|
|
53
|
+
DatePickerRangePicker: ComponentType<RangePickerProps>;
|
|
54
|
+
DatePickerTimePicker: ComponentType<TimePickerProps>;
|
|
55
|
+
ToggleSelect: ComponentType<ToggleSelectProps<string | number | boolean | null>>;
|
|
56
|
+
ToggleSelectMulti: ComponentType<ToggleSelectMultiProps>;
|
|
57
|
+
LoadingSpin: ComponentType<SpinProps>;
|
|
58
|
+
LoadingSkeleton: ComponentType<SkeletonProps>;
|
|
59
|
+
LoadingProgressBar: ComponentType<ProgressBarProps>;
|
|
60
|
+
LoadingButton: ComponentType<LoadingButtonProps>;
|
|
61
|
+
LoadingInput: ComponentType<LoadingInputProps>;
|
|
62
|
+
LoadingArea: ComponentType<Record<string, never>>;
|
|
63
|
+
}
|
|
64
|
+
export type AkanUiOverrideName = keyof AkanUiOverrides;
|
|
65
|
+
/** Prop contract an app-authored Modal override must satisfy. */
|
|
66
|
+
export type AkanModalComponent = AkanUiOverrides["Modal"];
|
|
67
|
+
/**
|
|
68
|
+
* Holds the override map for the current route subtree. Empty at the root, then
|
|
69
|
+
* merged (child wins) by each nested `UiOverrideProvider`, mirroring how nested
|
|
70
|
+
* `_layout.tsx` / `_overrides.tsx` stack down the route tree.
|
|
71
|
+
*/
|
|
72
|
+
export declare const UiOverrideContext: import("react").Context<Partial<AkanUiOverrides>>;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { AkanUiOverrides } from "./context.d.ts";
|
|
2
|
+
/**
|
|
3
|
+
* Wraps a framework component so a matching `_overrides.tsx` entry replaces it,
|
|
4
|
+
* scoped to that route subtree. Falls back to `Default` when no override is
|
|
5
|
+
* active, so every existing call site keeps working untouched. The proxy erases
|
|
6
|
+
* its prop type internally and re-asserts the slot's public component type on
|
|
7
|
+
* the way out, so call sites stay fully typed against the slot contract.
|
|
8
|
+
*/
|
|
9
|
+
export declare const createOverridable: <K extends keyof AkanUiOverrides>(name: K, Default: AkanUiOverrides[K]) => AkanUiOverrides[K];
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export type { AkanModalComponent, AkanUiOverrideName, AkanUiOverrides } from "./context.d.ts";
|
|
2
|
+
export { createOverridable } from "./createOverridable.d.ts";
|
|
3
|
+
export { override } from "./override.d.ts";
|
|
4
|
+
export { UiOverrideProvider, type UiOverrideProviderProps } from "./Provider.d.ts";
|
|
5
|
+
export { useUiOverride } from "./useUiOverride.d.ts";
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { AkanUiOverrides } from "./context.d.ts";
|
|
2
|
+
/**
|
|
3
|
+
* Typed manifest builder for a `page/_overrides.tsx` file:
|
|
4
|
+
*
|
|
5
|
+
* ```tsx
|
|
6
|
+
* export default override({ Modal: BrandModal });
|
|
7
|
+
* ```
|
|
8
|
+
*
|
|
9
|
+
* It type-checks each binding against that slot's public component contract and rejects unknown slot names as
|
|
10
|
+
* excess properties — so the manifest is validated at compile time and app components need no annotation of
|
|
11
|
+
* their own. Keys are the framework component names (`Modal`), matching `useUiOverride` and the `<Modal>` call
|
|
12
|
+
* sites.
|
|
13
|
+
*
|
|
14
|
+
* It is a pure, server-safe identity function (no React, no `createContext`), so the manifest needs no
|
|
15
|
+
* `"use client"` directive. The framework generates a `"use client"` wrapper layout that reads this map and
|
|
16
|
+
* mounts the override provider around the route subtree (see `writeGeneratedOverridesLayoutFile`).
|
|
17
|
+
*/
|
|
18
|
+
export declare const override: (overrides: Partial<AkanUiOverrides>) => Partial<AkanUiOverrides>;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type AkanUiOverrides } from "./context.d.ts";
|
|
2
|
+
/**
|
|
3
|
+
* Resolves the active override for a framework component in the current route
|
|
4
|
+
* subtree, or `undefined` when no `_overrides.tsx` in the route's ancestry
|
|
5
|
+
* declares one.
|
|
6
|
+
*/
|
|
7
|
+
export declare const useUiOverride: <K extends keyof AkanUiOverrides>(name: K) => AkanUiOverrides[K] | undefined;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./UiOverride/index.d.ts";
|