@tiny-server/core 0.0.0 → 0.0.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/dist/app/features/extensions/Extension.d.ts +3 -3
- package/dist/app/features/extensions/types.d.ts +7 -4
- package/dist/app/features/menu/types.d.ts +7 -3
- package/dist/app/features/modals/types.d.ts +7 -4
- package/package.json +3 -2
- package/src/app/features/extensions/Extension.tsx +69 -69
- package/src/app/features/extensions/types.tsx +52 -52
- package/src/app/features/menu/Menu.tsx +66 -66
- package/src/app/features/menu/types.ts +84 -84
- package/src/app/features/modals/types.ts +94 -94
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ReactNode } from 'react';
|
|
2
2
|
import { ExtensionName, ExtensionParams } from './types';
|
|
3
|
-
export interface ExtensionProps<TName extends ExtensionName
|
|
3
|
+
export interface ExtensionProps<TName extends ExtensionName> {
|
|
4
4
|
/**
|
|
5
5
|
* The name of the extension to render.
|
|
6
6
|
*/
|
|
@@ -8,7 +8,7 @@ export interface ExtensionProps<TName extends ExtensionName | (string & {})> {
|
|
|
8
8
|
/**
|
|
9
9
|
* The parameters to be forwarded to the extension component.
|
|
10
10
|
*/
|
|
11
|
-
params:
|
|
11
|
+
params: ExtensionParams<TName>;
|
|
12
12
|
/**
|
|
13
13
|
* An optional fallback to render if:
|
|
14
14
|
* - there is no extension with the given name.
|
|
@@ -29,4 +29,4 @@ export interface ExtensionProps<TName extends ExtensionName | (string & {})> {
|
|
|
29
29
|
* Renders registered extension componentsmatching the given name.
|
|
30
30
|
* If no such extension components are known, renders a fallback.
|
|
31
31
|
*/
|
|
32
|
-
export declare function Extension<
|
|
32
|
+
export declare function Extension<TName extends ExtensionName>({ name, params, fallback, loadingFallback, errorFallback, }: ExtensionProps<TName>): string | number | bigint | boolean | Iterable<ReactNode> | Promise<string | number | bigint | boolean | import('react').ReactPortal | import('react').ReactElement<unknown, string | import('react').JSXElementConstructor<any>> | Iterable<ReactNode> | null | undefined> | import("react/jsx-runtime").JSX.Element | null | undefined;
|
|
@@ -12,16 +12,19 @@ import { ComponentType } from 'react';
|
|
|
12
12
|
* ```
|
|
13
13
|
*/
|
|
14
14
|
export interface ExtensionParamsMap {
|
|
15
|
-
'': never;
|
|
16
15
|
}
|
|
17
16
|
/**
|
|
18
|
-
* Defines all known extension names.
|
|
17
|
+
* Defines all typed/known extension names.
|
|
19
18
|
*/
|
|
20
|
-
export type
|
|
19
|
+
export type KnownExtensionName = keyof ExtensionParamsMap;
|
|
20
|
+
/**
|
|
21
|
+
* Defines an extension name, both typed/known and untyped/unknown.
|
|
22
|
+
*/
|
|
23
|
+
export type ExtensionName = KnownExtensionName | (string & {});
|
|
21
24
|
/**
|
|
22
25
|
* The parameters of the extension with the given `TName`.
|
|
23
26
|
*/
|
|
24
|
-
export type ExtensionParams<TName extends ExtensionName> = ExtensionParamsMap[TName]
|
|
27
|
+
export type ExtensionParams<TName extends ExtensionName> = TName extends KnownExtensionName ? ExtensionParamsMap[TName] : Record<string, unknown>;
|
|
25
28
|
export interface ExtensionComponentProps<TName extends ExtensionName> {
|
|
26
29
|
/**
|
|
27
30
|
* The parameters to be forwarded to the extension component.
|
|
@@ -23,13 +23,17 @@ export interface MenuParamsMap {
|
|
|
23
23
|
default: {};
|
|
24
24
|
}
|
|
25
25
|
/**
|
|
26
|
-
* Defines all known menu types.
|
|
26
|
+
* Defines all typed/known menu types.
|
|
27
27
|
*/
|
|
28
|
-
export type
|
|
28
|
+
export type KnownMenuType = keyof MenuParamsMap;
|
|
29
|
+
/**
|
|
30
|
+
* Defines a menu type, both typed/known and untyped/unknown.
|
|
31
|
+
*/
|
|
32
|
+
export type MenuType = KnownMenuType | (string & {});
|
|
29
33
|
/**
|
|
30
34
|
* The parameters of the menu with the given `TType`.
|
|
31
35
|
*/
|
|
32
|
-
export type MenuParams<TType extends MenuType> = MenuParamsMap[TType];
|
|
36
|
+
export type MenuParams<TType extends MenuType> = TType extends KnownMenuType ? MenuParamsMap[TType] : unknown;
|
|
33
37
|
export interface MenuComponentProps<TMenuType extends MenuType = MenuType> extends PropsWithChildren {
|
|
34
38
|
/**
|
|
35
39
|
* The type of the menu that the item belongs to.
|
|
@@ -32,16 +32,19 @@ declare module '../../types' {
|
|
|
32
32
|
* ```
|
|
33
33
|
*/
|
|
34
34
|
export interface ModalParamsMap {
|
|
35
|
-
'': never;
|
|
36
35
|
}
|
|
37
36
|
/**
|
|
38
|
-
* Defines all known modal
|
|
37
|
+
* Defines all typed/known modal names.
|
|
39
38
|
*/
|
|
40
|
-
export type
|
|
39
|
+
export type KnownModalName = keyof ModalParamsMap;
|
|
40
|
+
/**
|
|
41
|
+
* Defines a modal name, both typed/known and untyped/unknown.
|
|
42
|
+
*/
|
|
43
|
+
export type ModalName = KnownModalName | (string & {});
|
|
41
44
|
/**
|
|
42
45
|
* The parameters of the modal with the given `TName`.
|
|
43
46
|
*/
|
|
44
|
-
export type ModalParams<TName extends ModalName> = ModalParamsMap[TName];
|
|
47
|
+
export type ModalParams<TName extends ModalName> = TName extends KnownModalName ? ModalParamsMap[TName] : unknown;
|
|
45
48
|
export interface ModalComponentProps<TName extends ModalName> {
|
|
46
49
|
/**
|
|
47
50
|
* The modal name.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tiny-server/core",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.1",
|
|
4
4
|
"description": "The Tiny Server core frontend package.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"tiny-server",
|
|
@@ -48,5 +48,6 @@
|
|
|
48
48
|
"react-i18next": "^17",
|
|
49
49
|
"react-router": "^7",
|
|
50
50
|
"zustand": "^5"
|
|
51
|
-
}
|
|
51
|
+
},
|
|
52
|
+
"gitHead": "446a1f901f369fb4534e20fd3cf95c71ca2cbbf1"
|
|
52
53
|
}
|
|
@@ -1,69 +1,69 @@
|
|
|
1
|
-
import { type ReactNode, useMemo, Suspense, type ComponentType } from 'react';
|
|
2
|
-
import { ErrorBoundary } from '../../../components';
|
|
3
|
-
import type { ExtensionComponentProps, ExtensionName, ExtensionParams } from './types';
|
|
4
|
-
import { useApp } from '../../context';
|
|
5
|
-
|
|
6
|
-
export interface ExtensionProps<TName extends ExtensionName> {
|
|
7
|
-
/**
|
|
8
|
-
* The name of the extension to render.
|
|
9
|
-
*/
|
|
10
|
-
name: TName;
|
|
11
|
-
/**
|
|
12
|
-
* The parameters to be forwarded to the extension component.
|
|
13
|
-
*/
|
|
14
|
-
params: ExtensionParams<TName>;
|
|
15
|
-
/**
|
|
16
|
-
* An optional fallback to render if:
|
|
17
|
-
* - there is no extension with the given name.
|
|
18
|
-
* - no {@link loadingFallback} is provided and the extension is loading.
|
|
19
|
-
* - no {@link errorFallback} is provided and the extension errored.
|
|
20
|
-
*/
|
|
21
|
-
fallback?: ReactNode;
|
|
22
|
-
/**
|
|
23
|
-
* An optional fallback to render while the extension is being lazy-loaded.
|
|
24
|
-
*/
|
|
25
|
-
loadingFallback?: ReactNode;
|
|
26
|
-
/**
|
|
27
|
-
* An optional fallback to render if an error is caught while rendering the extension.
|
|
28
|
-
*/
|
|
29
|
-
errorFallback?: ReactNode;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Renders registered extension componentsmatching the given name.
|
|
34
|
-
* If no such extension components are known, renders a fallback.
|
|
35
|
-
*/
|
|
36
|
-
export function Extension<TName extends ExtensionName>({
|
|
37
|
-
name,
|
|
38
|
-
params,
|
|
39
|
-
fallback,
|
|
40
|
-
loadingFallback,
|
|
41
|
-
errorFallback,
|
|
42
|
-
}: ExtensionProps<TName>) {
|
|
43
|
-
const app = useApp();
|
|
44
|
-
const { Loading, Error } = app.components;
|
|
45
|
-
|
|
46
|
-
loadingFallback ??= <Loading kind="extension" />;
|
|
47
|
-
errorFallback ??= <Error kind="extension" />;
|
|
48
|
-
|
|
49
|
-
const extensionComponents = useMemo(() => {
|
|
50
|
-
return app.modules
|
|
51
|
-
.flatMap((m) => m.extensions)
|
|
52
|
-
.filter((registration) => registration.name === name)
|
|
53
|
-
.map((registration) => registration.component) as Array<ComponentType<ExtensionComponentProps<ExtensionName>>>;
|
|
54
|
-
}, [name, app.modules]);
|
|
55
|
-
|
|
56
|
-
if (extensionComponents.length === 0) {
|
|
57
|
-
return fallback;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
return (
|
|
61
|
-
<ErrorBoundary fallback={errorFallback ?? fallback}>
|
|
62
|
-
<Suspense fallback={loadingFallback ?? fallback}>
|
|
63
|
-
{extensionComponents.map((ExtensionComponent, index) => (
|
|
64
|
-
<ExtensionComponent key={index} params={params} />
|
|
65
|
-
))}
|
|
66
|
-
</Suspense>
|
|
67
|
-
</ErrorBoundary>
|
|
68
|
-
);
|
|
69
|
-
}
|
|
1
|
+
import { type ReactNode, useMemo, Suspense, type ComponentType } from 'react';
|
|
2
|
+
import { ErrorBoundary } from '../../../components';
|
|
3
|
+
import type { ExtensionComponentProps, ExtensionName, ExtensionParams } from './types';
|
|
4
|
+
import { useApp } from '../../context';
|
|
5
|
+
|
|
6
|
+
export interface ExtensionProps<TName extends ExtensionName> {
|
|
7
|
+
/**
|
|
8
|
+
* The name of the extension to render.
|
|
9
|
+
*/
|
|
10
|
+
name: TName;
|
|
11
|
+
/**
|
|
12
|
+
* The parameters to be forwarded to the extension component.
|
|
13
|
+
*/
|
|
14
|
+
params: ExtensionParams<TName>;
|
|
15
|
+
/**
|
|
16
|
+
* An optional fallback to render if:
|
|
17
|
+
* - there is no extension with the given name.
|
|
18
|
+
* - no {@link loadingFallback} is provided and the extension is loading.
|
|
19
|
+
* - no {@link errorFallback} is provided and the extension errored.
|
|
20
|
+
*/
|
|
21
|
+
fallback?: ReactNode;
|
|
22
|
+
/**
|
|
23
|
+
* An optional fallback to render while the extension is being lazy-loaded.
|
|
24
|
+
*/
|
|
25
|
+
loadingFallback?: ReactNode;
|
|
26
|
+
/**
|
|
27
|
+
* An optional fallback to render if an error is caught while rendering the extension.
|
|
28
|
+
*/
|
|
29
|
+
errorFallback?: ReactNode;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Renders registered extension componentsmatching the given name.
|
|
34
|
+
* If no such extension components are known, renders a fallback.
|
|
35
|
+
*/
|
|
36
|
+
export function Extension<TName extends ExtensionName>({
|
|
37
|
+
name,
|
|
38
|
+
params,
|
|
39
|
+
fallback,
|
|
40
|
+
loadingFallback,
|
|
41
|
+
errorFallback,
|
|
42
|
+
}: ExtensionProps<TName>) {
|
|
43
|
+
const app = useApp();
|
|
44
|
+
const { Loading, Error } = app.components;
|
|
45
|
+
|
|
46
|
+
loadingFallback ??= <Loading kind="extension" />;
|
|
47
|
+
errorFallback ??= <Error kind="extension" />;
|
|
48
|
+
|
|
49
|
+
const extensionComponents = useMemo(() => {
|
|
50
|
+
return app.modules
|
|
51
|
+
.flatMap((m) => m.extensions)
|
|
52
|
+
.filter((registration) => registration.name === name)
|
|
53
|
+
.map((registration) => registration.component) as Array<ComponentType<ExtensionComponentProps<ExtensionName>>>;
|
|
54
|
+
}, [name, app.modules]);
|
|
55
|
+
|
|
56
|
+
if (extensionComponents.length === 0) {
|
|
57
|
+
return fallback;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<ErrorBoundary fallback={errorFallback ?? fallback}>
|
|
62
|
+
<Suspense fallback={loadingFallback ?? fallback}>
|
|
63
|
+
{extensionComponents.map((ExtensionComponent, index) => (
|
|
64
|
+
<ExtensionComponent key={index} params={params} />
|
|
65
|
+
))}
|
|
66
|
+
</Suspense>
|
|
67
|
+
</ErrorBoundary>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
@@ -1,52 +1,52 @@
|
|
|
1
|
-
import type { ComponentType } from 'react';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Maps extension keys to their corresponding component props.
|
|
5
|
-
* This interface can be extended by modules via declaration merging.
|
|
6
|
-
*
|
|
7
|
-
* ```ts
|
|
8
|
-
* declare module '@tiny-server/core' {
|
|
9
|
-
* export interface ExtensionParamsMap {
|
|
10
|
-
* 'my-extension': MyExtensionProps;
|
|
11
|
-
* }
|
|
12
|
-
* }
|
|
13
|
-
* ```
|
|
14
|
-
*/
|
|
15
|
-
export interface ExtensionParamsMap {}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Defines all typed/known extension names.
|
|
19
|
-
*/
|
|
20
|
-
export type KnownExtensionName = keyof ExtensionParamsMap;
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Defines an extension name, both typed/known and untyped/unknown.
|
|
24
|
-
*/
|
|
25
|
-
export type ExtensionName = KnownExtensionName | (string & {});
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* The parameters of the extension with the given `TName`.
|
|
29
|
-
*/
|
|
30
|
-
export type ExtensionParams<TName extends ExtensionName> = TName extends KnownExtensionName
|
|
31
|
-
? ExtensionParamsMap[TName]
|
|
32
|
-
: Record<string, unknown>;
|
|
33
|
-
|
|
34
|
-
export interface ExtensionComponentProps<TName extends ExtensionName> {
|
|
35
|
-
/**
|
|
36
|
-
* The parameters to be forwarded to the extension component.
|
|
37
|
-
*/
|
|
38
|
-
params: ExtensionParams<TName>;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export interface ExtensionRegistration<TName extends ExtensionName> {
|
|
42
|
-
/**
|
|
43
|
-
* The name of the extension to be registered.
|
|
44
|
-
*/
|
|
45
|
-
name: TName;
|
|
46
|
-
/**
|
|
47
|
-
* The component to be rendered for the extension.
|
|
48
|
-
*/
|
|
49
|
-
component: ComponentType<ExtensionComponentProps<TName>>;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export type AnyExtensionRegistration = { [Key in ExtensionName]: ExtensionRegistration<Key> }[ExtensionName];
|
|
1
|
+
import type { ComponentType } from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Maps extension keys to their corresponding component props.
|
|
5
|
+
* This interface can be extended by modules via declaration merging.
|
|
6
|
+
*
|
|
7
|
+
* ```ts
|
|
8
|
+
* declare module '@tiny-server/core' {
|
|
9
|
+
* export interface ExtensionParamsMap {
|
|
10
|
+
* 'my-extension': MyExtensionProps;
|
|
11
|
+
* }
|
|
12
|
+
* }
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
export interface ExtensionParamsMap {}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Defines all typed/known extension names.
|
|
19
|
+
*/
|
|
20
|
+
export type KnownExtensionName = keyof ExtensionParamsMap;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Defines an extension name, both typed/known and untyped/unknown.
|
|
24
|
+
*/
|
|
25
|
+
export type ExtensionName = KnownExtensionName | (string & {});
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The parameters of the extension with the given `TName`.
|
|
29
|
+
*/
|
|
30
|
+
export type ExtensionParams<TName extends ExtensionName> = TName extends KnownExtensionName
|
|
31
|
+
? ExtensionParamsMap[TName]
|
|
32
|
+
: Record<string, unknown>;
|
|
33
|
+
|
|
34
|
+
export interface ExtensionComponentProps<TName extends ExtensionName> {
|
|
35
|
+
/**
|
|
36
|
+
* The parameters to be forwarded to the extension component.
|
|
37
|
+
*/
|
|
38
|
+
params: ExtensionParams<TName>;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface ExtensionRegistration<TName extends ExtensionName> {
|
|
42
|
+
/**
|
|
43
|
+
* The name of the extension to be registered.
|
|
44
|
+
*/
|
|
45
|
+
name: TName;
|
|
46
|
+
/**
|
|
47
|
+
* The component to be rendered for the extension.
|
|
48
|
+
*/
|
|
49
|
+
component: ComponentType<ExtensionComponentProps<TName>>;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export type AnyExtensionRegistration = { [Key in ExtensionName]: ExtensionRegistration<Key> }[ExtensionName];
|
|
@@ -1,66 +1,66 @@
|
|
|
1
|
-
import { Suspense, useMemo, type ComponentType } from 'react';
|
|
2
|
-
import type {
|
|
3
|
-
AnyMenuItemRegistration,
|
|
4
|
-
MenuComponentProps,
|
|
5
|
-
MenuItemComponentProps,
|
|
6
|
-
MenuParams,
|
|
7
|
-
MenuType,
|
|
8
|
-
} from './types';
|
|
9
|
-
import { ErrorBoundary } from '../../../components';
|
|
10
|
-
import { useApp } from '../../context';
|
|
11
|
-
|
|
12
|
-
export interface MenuProps<TMenuType extends MenuType = MenuType> extends MenuComponentProps<TMenuType> {}
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Renders the menu of the given type.
|
|
16
|
-
*/
|
|
17
|
-
export function Menu({ type, params }: MenuProps) {
|
|
18
|
-
const {
|
|
19
|
-
components: { MenuContainer, Loading, Error },
|
|
20
|
-
modules,
|
|
21
|
-
} = useApp();
|
|
22
|
-
|
|
23
|
-
const items = useMemo(() => {
|
|
24
|
-
const itemsOfType = modules.flatMap((m) => m.menuItems).filter((item) => item.menu === type);
|
|
25
|
-
const rootItems = itemsOfType.filter((item) => !item.parentId);
|
|
26
|
-
return rootItems.map((item) => (
|
|
27
|
-
<MenuItem key={item.id} type={type} params={params} thisItem={item} allItems={itemsOfType} />
|
|
28
|
-
));
|
|
29
|
-
}, [type, params, modules]);
|
|
30
|
-
|
|
31
|
-
return (
|
|
32
|
-
<ErrorBoundary fallback={<Error kind="menu" />}>
|
|
33
|
-
<Suspense fallback={<Loading kind="menu" />}>
|
|
34
|
-
<MenuContainer type={type} params={params}>
|
|
35
|
-
{items}
|
|
36
|
-
</MenuContainer>
|
|
37
|
-
</Suspense>
|
|
38
|
-
</ErrorBoundary>
|
|
39
|
-
);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
interface MenuItemProps {
|
|
43
|
-
type: MenuType;
|
|
44
|
-
params: MenuParams<MenuType>;
|
|
45
|
-
thisItem: AnyMenuItemRegistration;
|
|
46
|
-
allItems: Array<AnyMenuItemRegistration>;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function MenuItem({ type, params, thisItem, allItems }: MenuItemProps) {
|
|
50
|
-
const Component = thisItem.component as ComponentType<MenuItemComponentProps>;
|
|
51
|
-
|
|
52
|
-
const children = useMemo(() => {
|
|
53
|
-
const childItems = allItems
|
|
54
|
-
.filter((item) => item.parentId === thisItem.id)
|
|
55
|
-
.map((item) => <MenuItem key={item.id} type={type} params={params} thisItem={item} allItems={allItems} />);
|
|
56
|
-
|
|
57
|
-
// Do not return empty arrays. This plays better with Mantine's NavLink children.
|
|
58
|
-
return childItems.length > 0 ? childItems : undefined;
|
|
59
|
-
}, [type, params, thisItem, allItems]);
|
|
60
|
-
|
|
61
|
-
return (
|
|
62
|
-
<Component id={thisItem.id} type={type} params={params}>
|
|
63
|
-
{children}
|
|
64
|
-
</Component>
|
|
65
|
-
);
|
|
66
|
-
}
|
|
1
|
+
import { Suspense, useMemo, type ComponentType } from 'react';
|
|
2
|
+
import type {
|
|
3
|
+
AnyMenuItemRegistration,
|
|
4
|
+
MenuComponentProps,
|
|
5
|
+
MenuItemComponentProps,
|
|
6
|
+
MenuParams,
|
|
7
|
+
MenuType,
|
|
8
|
+
} from './types';
|
|
9
|
+
import { ErrorBoundary } from '../../../components';
|
|
10
|
+
import { useApp } from '../../context';
|
|
11
|
+
|
|
12
|
+
export interface MenuProps<TMenuType extends MenuType = MenuType> extends MenuComponentProps<TMenuType> {}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Renders the menu of the given type.
|
|
16
|
+
*/
|
|
17
|
+
export function Menu({ type, params }: MenuProps) {
|
|
18
|
+
const {
|
|
19
|
+
components: { MenuContainer, Loading, Error },
|
|
20
|
+
modules,
|
|
21
|
+
} = useApp();
|
|
22
|
+
|
|
23
|
+
const items = useMemo(() => {
|
|
24
|
+
const itemsOfType = modules.flatMap((m) => m.menuItems).filter((item) => item.menu === type);
|
|
25
|
+
const rootItems = itemsOfType.filter((item) => !item.parentId);
|
|
26
|
+
return rootItems.map((item) => (
|
|
27
|
+
<MenuItem key={item.id} type={type} params={params} thisItem={item} allItems={itemsOfType} />
|
|
28
|
+
));
|
|
29
|
+
}, [type, params, modules]);
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<ErrorBoundary fallback={<Error kind="menu" />}>
|
|
33
|
+
<Suspense fallback={<Loading kind="menu" />}>
|
|
34
|
+
<MenuContainer type={type} params={params}>
|
|
35
|
+
{items}
|
|
36
|
+
</MenuContainer>
|
|
37
|
+
</Suspense>
|
|
38
|
+
</ErrorBoundary>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
interface MenuItemProps {
|
|
43
|
+
type: MenuType;
|
|
44
|
+
params: MenuParams<MenuType>;
|
|
45
|
+
thisItem: AnyMenuItemRegistration;
|
|
46
|
+
allItems: Array<AnyMenuItemRegistration>;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function MenuItem({ type, params, thisItem, allItems }: MenuItemProps) {
|
|
50
|
+
const Component = thisItem.component as ComponentType<MenuItemComponentProps>;
|
|
51
|
+
|
|
52
|
+
const children = useMemo(() => {
|
|
53
|
+
const childItems = allItems
|
|
54
|
+
.filter((item) => item.parentId === thisItem.id)
|
|
55
|
+
.map((item) => <MenuItem key={item.id} type={type} params={params} thisItem={item} allItems={allItems} />);
|
|
56
|
+
|
|
57
|
+
// Do not return empty arrays. This plays better with Mantine's NavLink children.
|
|
58
|
+
return childItems.length > 0 ? childItems : undefined;
|
|
59
|
+
}, [type, params, thisItem, allItems]);
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<Component id={thisItem.id} type={type} params={params}>
|
|
63
|
+
{children}
|
|
64
|
+
</Component>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
@@ -1,84 +1,84 @@
|
|
|
1
|
-
import type { ComponentType, PropsWithChildren } from 'react';
|
|
2
|
-
import type { MenuContainerProps } from './MenuContainer';
|
|
3
|
-
import type { MenuItemProps } from './MenuItem';
|
|
4
|
-
|
|
5
|
-
declare module '../../types' {
|
|
6
|
-
export interface AppComponents {
|
|
7
|
-
MenuContainer: ComponentType<MenuContainerProps>;
|
|
8
|
-
MenuItem: ComponentType<MenuItemProps>;
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* A map of all known menus.
|
|
14
|
-
* This interface can be extended by modules via declaration merging.
|
|
15
|
-
*
|
|
16
|
-
* ```ts
|
|
17
|
-
* declare module '@tiny-server/core' {
|
|
18
|
-
* export interface MenuParamsMap {
|
|
19
|
-
* 'my-menu': MyMenuParams;
|
|
20
|
-
* }
|
|
21
|
-
* }
|
|
22
|
-
* ```
|
|
23
|
-
*/
|
|
24
|
-
export interface MenuParamsMap {
|
|
25
|
-
default: {};
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Defines all typed/known menu types.
|
|
30
|
-
*/
|
|
31
|
-
export type KnownMenuType = keyof MenuParamsMap;
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Defines a menu type, both typed/known and untyped/unknown.
|
|
35
|
-
*/
|
|
36
|
-
export type MenuType = KnownMenuType | (string & {});
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* The parameters of the menu with the given `TType`.
|
|
40
|
-
*/
|
|
41
|
-
export type MenuParams<TType extends MenuType> = TType extends KnownMenuType ? MenuParamsMap[TType] : unknown;
|
|
42
|
-
|
|
43
|
-
export interface MenuComponentProps<TMenuType extends MenuType = MenuType> extends PropsWithChildren {
|
|
44
|
-
/**
|
|
45
|
-
* The type of the menu that the item belongs to.
|
|
46
|
-
*/
|
|
47
|
-
type: TMenuType;
|
|
48
|
-
/**
|
|
49
|
-
* The parameters to be forwarded to the menu item component.
|
|
50
|
-
*/
|
|
51
|
-
params: MenuParams<TMenuType>;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export interface MenuItemComponentProps<TMenuType extends MenuType = MenuType> extends MenuComponentProps<TMenuType> {
|
|
55
|
-
/**
|
|
56
|
-
* The unique identifier of the menu item.
|
|
57
|
-
*/
|
|
58
|
-
id: string;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export interface MenuItemRegistration<TMenuType extends MenuType> {
|
|
62
|
-
/**
|
|
63
|
-
* A unique identifier for the menu item.
|
|
64
|
-
*/
|
|
65
|
-
id: string;
|
|
66
|
-
/**
|
|
67
|
-
* The unique identifier of the parent menu item.
|
|
68
|
-
* Must reference an existing parent menu item within the same menu.
|
|
69
|
-
* If no parent is found, the child is not rendered.
|
|
70
|
-
*/
|
|
71
|
-
parentId?: string;
|
|
72
|
-
/**
|
|
73
|
-
* The menu that the item belongs to.
|
|
74
|
-
*/
|
|
75
|
-
menu: TMenuType;
|
|
76
|
-
/**
|
|
77
|
-
* The component to be rendered for the menu item.
|
|
78
|
-
*/
|
|
79
|
-
component: ComponentType<MenuItemComponentProps<TMenuType>>;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
export type AnyMenuItemRegistration = {
|
|
83
|
-
[TMenuType in MenuType]: MenuItemRegistration<TMenuType>;
|
|
84
|
-
}[MenuType];
|
|
1
|
+
import type { ComponentType, PropsWithChildren } from 'react';
|
|
2
|
+
import type { MenuContainerProps } from './MenuContainer';
|
|
3
|
+
import type { MenuItemProps } from './MenuItem';
|
|
4
|
+
|
|
5
|
+
declare module '../../types' {
|
|
6
|
+
export interface AppComponents {
|
|
7
|
+
MenuContainer: ComponentType<MenuContainerProps>;
|
|
8
|
+
MenuItem: ComponentType<MenuItemProps>;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* A map of all known menus.
|
|
14
|
+
* This interface can be extended by modules via declaration merging.
|
|
15
|
+
*
|
|
16
|
+
* ```ts
|
|
17
|
+
* declare module '@tiny-server/core' {
|
|
18
|
+
* export interface MenuParamsMap {
|
|
19
|
+
* 'my-menu': MyMenuParams;
|
|
20
|
+
* }
|
|
21
|
+
* }
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export interface MenuParamsMap {
|
|
25
|
+
default: {};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Defines all typed/known menu types.
|
|
30
|
+
*/
|
|
31
|
+
export type KnownMenuType = keyof MenuParamsMap;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Defines a menu type, both typed/known and untyped/unknown.
|
|
35
|
+
*/
|
|
36
|
+
export type MenuType = KnownMenuType | (string & {});
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The parameters of the menu with the given `TType`.
|
|
40
|
+
*/
|
|
41
|
+
export type MenuParams<TType extends MenuType> = TType extends KnownMenuType ? MenuParamsMap[TType] : unknown;
|
|
42
|
+
|
|
43
|
+
export interface MenuComponentProps<TMenuType extends MenuType = MenuType> extends PropsWithChildren {
|
|
44
|
+
/**
|
|
45
|
+
* The type of the menu that the item belongs to.
|
|
46
|
+
*/
|
|
47
|
+
type: TMenuType;
|
|
48
|
+
/**
|
|
49
|
+
* The parameters to be forwarded to the menu item component.
|
|
50
|
+
*/
|
|
51
|
+
params: MenuParams<TMenuType>;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface MenuItemComponentProps<TMenuType extends MenuType = MenuType> extends MenuComponentProps<TMenuType> {
|
|
55
|
+
/**
|
|
56
|
+
* The unique identifier of the menu item.
|
|
57
|
+
*/
|
|
58
|
+
id: string;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface MenuItemRegistration<TMenuType extends MenuType> {
|
|
62
|
+
/**
|
|
63
|
+
* A unique identifier for the menu item.
|
|
64
|
+
*/
|
|
65
|
+
id: string;
|
|
66
|
+
/**
|
|
67
|
+
* The unique identifier of the parent menu item.
|
|
68
|
+
* Must reference an existing parent menu item within the same menu.
|
|
69
|
+
* If no parent is found, the child is not rendered.
|
|
70
|
+
*/
|
|
71
|
+
parentId?: string;
|
|
72
|
+
/**
|
|
73
|
+
* The menu that the item belongs to.
|
|
74
|
+
*/
|
|
75
|
+
menu: TMenuType;
|
|
76
|
+
/**
|
|
77
|
+
* The component to be rendered for the menu item.
|
|
78
|
+
*/
|
|
79
|
+
component: ComponentType<MenuItemComponentProps<TMenuType>>;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export type AnyMenuItemRegistration = {
|
|
83
|
+
[TMenuType in MenuType]: MenuItemRegistration<TMenuType>;
|
|
84
|
+
}[MenuType];
|
|
@@ -1,94 +1,94 @@
|
|
|
1
|
-
import type { ComponentType } from 'react';
|
|
2
|
-
import type { ModalProps } from './Modal';
|
|
3
|
-
import type { ModalBodyProps } from './ModalBody';
|
|
4
|
-
import type { ModalFooterProps } from './ModalFooter';
|
|
5
|
-
import type { ModalHeaderProps } from './ModalHeader';
|
|
6
|
-
|
|
7
|
-
declare module '../../types' {
|
|
8
|
-
export interface AppInstance {
|
|
9
|
-
/**
|
|
10
|
-
* Shows a registered modal window.
|
|
11
|
-
* @param name The name of the modal to be shown.
|
|
12
|
-
* @param params The parameters to be passed to the modal.
|
|
13
|
-
*/
|
|
14
|
-
showModal<TModalName extends ModalName>(name: TModalName, params: ModalParams<TModalName>): void;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export interface AppComponents {
|
|
18
|
-
Modal: ComponentType<ModalProps>;
|
|
19
|
-
ModalBody: ComponentType<ModalBodyProps>;
|
|
20
|
-
ModalFooter: ComponentType<ModalFooterProps>;
|
|
21
|
-
ModalHeader: ComponentType<ModalHeaderProps>;
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* A map of all known modals.
|
|
27
|
-
* This interface can be extended by modules via declaration merging.
|
|
28
|
-
*
|
|
29
|
-
* ```ts
|
|
30
|
-
* declare module '@tiny-server/core' {
|
|
31
|
-
* export interface ModalParamsMap {
|
|
32
|
-
* 'my-modal': MyModalParams;
|
|
33
|
-
* }
|
|
34
|
-
* }
|
|
35
|
-
* ```
|
|
36
|
-
*/
|
|
37
|
-
export interface ModalParamsMap {}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Defines all typed/known modal names.
|
|
41
|
-
*/
|
|
42
|
-
export type KnownModalName = keyof ModalParamsMap;
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Defines a modal name, both typed/known and untyped/unknown.
|
|
46
|
-
*/
|
|
47
|
-
export type ModalName = KnownModalName | (string & {});
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* The parameters of the modal with the given `TName`.
|
|
51
|
-
*/
|
|
52
|
-
export type ModalParams<TName extends ModalName> = TName extends KnownModalName ? ModalParamsMap[TName] : unknown;
|
|
53
|
-
|
|
54
|
-
export interface ModalComponentProps<TName extends ModalName> {
|
|
55
|
-
/**
|
|
56
|
-
* The modal name.
|
|
57
|
-
*/
|
|
58
|
-
name: TName;
|
|
59
|
-
/**
|
|
60
|
-
* The parameters to be forwarded to the modal component.
|
|
61
|
-
*/
|
|
62
|
-
params: ModalParams<TName>;
|
|
63
|
-
/**
|
|
64
|
-
* Closes/unmounts the modal.
|
|
65
|
-
*/
|
|
66
|
-
onClose(): void;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Optional configuration values for the modal container.
|
|
71
|
-
*/
|
|
72
|
-
export interface ModalOptions {
|
|
73
|
-
/**
|
|
74
|
-
* The modal's size.
|
|
75
|
-
*/
|
|
76
|
-
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'auto' | (string & {});
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
export interface ModalRegistration<TName extends ModalName> {
|
|
80
|
-
/**
|
|
81
|
-
* The name of the modal to be registered.
|
|
82
|
-
*/
|
|
83
|
-
name: TName;
|
|
84
|
-
/**
|
|
85
|
-
* Optional configuration values for the modal container.
|
|
86
|
-
*/
|
|
87
|
-
options?: ModalOptions;
|
|
88
|
-
/**
|
|
89
|
-
* The component to be rendered for the modal.
|
|
90
|
-
*/
|
|
91
|
-
component: ComponentType<ModalComponentProps<TName>>;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
export type AnyModalRegistration = { [Key in ModalName]: ModalRegistration<Key> }[ModalName];
|
|
1
|
+
import type { ComponentType } from 'react';
|
|
2
|
+
import type { ModalProps } from './Modal';
|
|
3
|
+
import type { ModalBodyProps } from './ModalBody';
|
|
4
|
+
import type { ModalFooterProps } from './ModalFooter';
|
|
5
|
+
import type { ModalHeaderProps } from './ModalHeader';
|
|
6
|
+
|
|
7
|
+
declare module '../../types' {
|
|
8
|
+
export interface AppInstance {
|
|
9
|
+
/**
|
|
10
|
+
* Shows a registered modal window.
|
|
11
|
+
* @param name The name of the modal to be shown.
|
|
12
|
+
* @param params The parameters to be passed to the modal.
|
|
13
|
+
*/
|
|
14
|
+
showModal<TModalName extends ModalName>(name: TModalName, params: ModalParams<TModalName>): void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface AppComponents {
|
|
18
|
+
Modal: ComponentType<ModalProps>;
|
|
19
|
+
ModalBody: ComponentType<ModalBodyProps>;
|
|
20
|
+
ModalFooter: ComponentType<ModalFooterProps>;
|
|
21
|
+
ModalHeader: ComponentType<ModalHeaderProps>;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* A map of all known modals.
|
|
27
|
+
* This interface can be extended by modules via declaration merging.
|
|
28
|
+
*
|
|
29
|
+
* ```ts
|
|
30
|
+
* declare module '@tiny-server/core' {
|
|
31
|
+
* export interface ModalParamsMap {
|
|
32
|
+
* 'my-modal': MyModalParams;
|
|
33
|
+
* }
|
|
34
|
+
* }
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
export interface ModalParamsMap {}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Defines all typed/known modal names.
|
|
41
|
+
*/
|
|
42
|
+
export type KnownModalName = keyof ModalParamsMap;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Defines a modal name, both typed/known and untyped/unknown.
|
|
46
|
+
*/
|
|
47
|
+
export type ModalName = KnownModalName | (string & {});
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* The parameters of the modal with the given `TName`.
|
|
51
|
+
*/
|
|
52
|
+
export type ModalParams<TName extends ModalName> = TName extends KnownModalName ? ModalParamsMap[TName] : unknown;
|
|
53
|
+
|
|
54
|
+
export interface ModalComponentProps<TName extends ModalName> {
|
|
55
|
+
/**
|
|
56
|
+
* The modal name.
|
|
57
|
+
*/
|
|
58
|
+
name: TName;
|
|
59
|
+
/**
|
|
60
|
+
* The parameters to be forwarded to the modal component.
|
|
61
|
+
*/
|
|
62
|
+
params: ModalParams<TName>;
|
|
63
|
+
/**
|
|
64
|
+
* Closes/unmounts the modal.
|
|
65
|
+
*/
|
|
66
|
+
onClose(): void;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Optional configuration values for the modal container.
|
|
71
|
+
*/
|
|
72
|
+
export interface ModalOptions {
|
|
73
|
+
/**
|
|
74
|
+
* The modal's size.
|
|
75
|
+
*/
|
|
76
|
+
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'auto' | (string & {});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface ModalRegistration<TName extends ModalName> {
|
|
80
|
+
/**
|
|
81
|
+
* The name of the modal to be registered.
|
|
82
|
+
*/
|
|
83
|
+
name: TName;
|
|
84
|
+
/**
|
|
85
|
+
* Optional configuration values for the modal container.
|
|
86
|
+
*/
|
|
87
|
+
options?: ModalOptions;
|
|
88
|
+
/**
|
|
89
|
+
* The component to be rendered for the modal.
|
|
90
|
+
*/
|
|
91
|
+
component: ComponentType<ModalComponentProps<TName>>;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export type AnyModalRegistration = { [Key in ModalName]: ModalRegistration<Key> }[ModalName];
|