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.
Files changed (53) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/client/csrTypes.ts +2 -0
  3. package/common/routeConvention.ts +8 -5
  4. package/package.json +1 -1
  5. package/server/routeTreeBuilder.ts +37 -4
  6. package/types/client/csrTypes.d.ts +2 -0
  7. package/types/common/routeConvention.d.ts +1 -1
  8. package/types/ui/Button.d.ts +6 -1
  9. package/types/ui/DatePicker.d.ts +12 -9
  10. package/types/ui/Dropdown.d.ts +6 -1
  11. package/types/ui/Empty.d.ts +7 -1
  12. package/types/ui/Input.d.ts +12 -8
  13. package/types/ui/Loading/index.d.ts +11 -6
  14. package/types/ui/Menu.d.ts +9 -4
  15. package/types/ui/Modal.d.ts +11 -1
  16. package/types/ui/Pagination.d.ts +6 -1
  17. package/types/ui/Popconfirm.d.ts +7 -1
  18. package/types/ui/Radio.d.ts +7 -4
  19. package/types/ui/Select.d.ts +6 -1
  20. package/types/ui/Table.d.ts +6 -1
  21. package/types/ui/ToggleSelect.d.ts +11 -7
  22. package/types/ui/UiOverride/Provider.d.ts +14 -0
  23. package/types/ui/UiOverride/context.d.ts +72 -0
  24. package/types/ui/UiOverride/createOverridable.d.ts +9 -0
  25. package/types/ui/UiOverride/index.d.ts +5 -0
  26. package/types/ui/UiOverride/override.d.ts +18 -0
  27. package/types/ui/UiOverride/useUiOverride.d.ts +7 -0
  28. package/types/ui/UiOverride.d.ts +1 -0
  29. package/types/ui/Unauthorized.d.ts +8 -3
  30. package/types/ui/index.d.ts +1 -0
  31. package/ui/Button.tsx +15 -2
  32. package/ui/DatePicker.tsx +19 -9
  33. package/ui/Dropdown.tsx +9 -1
  34. package/ui/Empty.tsx +11 -1
  35. package/ui/Input.tsx +22 -14
  36. package/ui/Loading/index.tsx +15 -1
  37. package/ui/Menu.tsx +12 -3
  38. package/ui/Modal.tsx +13 -1
  39. package/ui/Pagination.tsx +9 -1
  40. package/ui/Popconfirm.tsx +9 -1
  41. package/ui/Radio.tsx +14 -3
  42. package/ui/Select.tsx +22 -2
  43. package/ui/Table.tsx +8 -1
  44. package/ui/ToggleSelect.tsx +31 -5
  45. package/ui/UiOverride/Provider.tsx +22 -0
  46. package/ui/UiOverride/context.ts +85 -0
  47. package/ui/UiOverride/createOverridable.tsx +25 -0
  48. package/ui/UiOverride/index.ts +5 -0
  49. package/ui/UiOverride/override.ts +19 -0
  50. package/ui/UiOverride/useUiOverride.ts +15 -0
  51. package/ui/Unauthorized.tsx +12 -3
  52. package/ui/index.ts +10 -0
  53. 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
@@ -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
- export type RouteModuleKind = "page" | "layout";
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 _layout.tsx are allowed as reserved route files under page/: ${displayPath}`,
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 = leaf === "_layout" || isInternalRootLayout ? "layout" : "page";
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 === "_layout" || leaf === "_index" || isInternalRootLayout ? moduleSegments.slice(0, -1) : moduleSegments;
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "akanjs",
3
- "version": "2.3.11-rc.9",
3
+ "version": "2.3.11",
4
4
  "sourceType": "module",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -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 = route.pageIncludesOwnLayout === false && currentLayout ? parentLayouts : renderLayouts;
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: "page" | "layout", loader: () => Promise<RouteModule>) {
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: "page" | "layout", mod: RouteModule) {
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;
@@ -1,4 +1,4 @@
1
- export type RouteModuleKind = "page" | "layout";
1
+ export type RouteModuleKind = "page" | "layout" | "overrides";
2
2
  export interface ParsedRouteModuleKey {
3
3
  key: string;
4
4
  kind: RouteModuleKind;
@@ -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
- export declare const Button: <Result = unknown>({ className, children, onClick, onSuccess, ...rest }: ButtonProps<Result>) => import("react/jsx-runtime").JSX.Element;
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>>;
@@ -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 declare const DatePicker: {
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
- export {};
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
+ };
@@ -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 Dropdown: ({ value, content, className, buttonClassName, dropdownClassName }: DropdownProps) => import("react/jsx-runtime").JSX.Element;
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>;
@@ -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 Empty: ({ className, description, children, minHeight }: EmptyProps) => import("react/jsx-runtime").JSX.Element;
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>;
@@ -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: () => import("react/jsx-runtime").JSX.Element;
3
- Button: ({ className, active, style }: import("./Button.d.ts").LoadingProps) => import("react/jsx-runtime").JSX.Element;
4
- Input: ({ className, active, style }: import("./Input.d.ts").LoadingProps) => import("react/jsx-runtime").JSX.Element;
5
- ProgressBar: ({ className, value, max }: import("./ProgressBar.d.ts").ProgressBarProps) => import("react/jsx-runtime").JSX.Element;
6
- Skeleton: ({ className, active, style }: import("./Skeleton.d.ts").SkeletonProps) => import("react/jsx-runtime").JSX.Element;
7
- Spin: ({ indicator, isCenter, className }: import("./Spin.d.ts").SpinProps) => import("react/jsx-runtime").JSX.Element;
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
  };
@@ -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 Menu: ({ items, onClick, selectedKeys, labelClassName, defaultSelectedKeys, className, ulClassName, liClassName, style, mode, activeStyle, inlineCollapsed, onMouseOver, onMouseLeave, }: MenuProps) => import("react/jsx-runtime").JSX.Element;
26
- export {};
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>;
@@ -16,4 +16,14 @@ export interface ModalProps {
16
16
  /** Ask for close confirmation before dismissing. */
17
17
  confirmClose?: boolean;
18
18
  }
19
- export declare const Modal: ({ className, title, action, open, onCancel, bodyClassName, children, confirmClose, }: ModalProps) => import("react/jsx-runtime").JSX.Element;
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>;
@@ -17,4 +17,9 @@ export interface PaginationProps {
17
17
  pageNumClassName?: string;
18
18
  };
19
19
  }
20
- export declare const Pagination: FC<PaginationProps>;
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>;
@@ -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 Popconfirm: ({ title, description, onConfirm, okButtonProps, cancelButtonProps, okText, cancelText, children, triggerClassName, decoClassName, }: PopconfirmProps) => import("react/jsx-runtime").JSX.Element;
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 {};
@@ -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
+ };
@@ -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
- 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>>({ label, desc, labelClassName, className, value, options, nullable, disabled, multiple, searchable, placeholder, selectClassName, selectorClassName, selectedClassName, onOpen, onChange, onSearch, renderOption, renderSelected, }: SelectProps<T, Multiple, Searchable, Option>) => import("react/jsx-runtime").JSX.Element;
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 {};
@@ -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 Table: ({ columns, dataSource, loading, size, bordered, pagination, showHeader, onRow, rowClassName, rowKey, }: TableProps) => import("react/jsx-runtime").JSX.Element;
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
- interface ToggleSelectProps<I extends string | number | boolean | null> {
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 declare const ToggleSelect: {
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
- export {};
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";