@developer_tribe/react-builder 1.0.1 → 1.0.3
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/AttributesEditor.d.ts +3 -1
- package/dist/DeviceMockFrame.d.ts +2 -1
- package/dist/RenderPage.d.ts +5 -3
- package/dist/attributes-editor/Field.d.ts +17 -0
- package/dist/attributes-editor/FieldInfoTooltip.d.ts +7 -0
- package/dist/attributes-editor/LayoutPreviewPicker.d.ts +12 -0
- package/dist/attributes-editor/SpecialCategorySection.d.ts +20 -0
- package/dist/attributes-editor/types.d.ts +14 -0
- package/dist/background.jpg +0 -0
- package/dist/build-components/BackgroundImage/BackgroundImage.d.ts +5 -0
- package/dist/build-components/BackgroundImage/BackgroundImageProps.generated.d.ts +44 -0
- package/dist/build-components/Button/Button.d.ts +1 -1
- package/dist/build-components/Button/ButtonProps.generated.d.ts +33 -1
- package/dist/build-components/Carousel/CarouselProps.generated.d.ts +34 -1
- package/dist/build-components/CarouselButtons/CarouselButtonsProps.generated.d.ts +32 -0
- package/dist/build-components/CarouselDots/CarouselDotsProps.generated.d.ts +32 -0
- package/dist/build-components/CarouselItem/CarouselItemProps.generated.d.ts +34 -1
- package/dist/build-components/CarouselProvider/CarouselProviderProps.generated.d.ts +34 -1
- package/dist/build-components/Image/ImageProps.generated.d.ts +32 -3
- package/dist/build-components/Onboard/OnboardProps.generated.d.ts +34 -1
- package/dist/build-components/OnboardButton/OnboardButtonProps.generated.d.ts +32 -0
- package/dist/build-components/OnboardButtons/OnboardButtonsProps.generated.d.ts +32 -0
- package/dist/build-components/OnboardDot/OnboardDot.d.ts +1 -1
- package/dist/build-components/OnboardDot/OnboardDotProps.generated.d.ts +29 -0
- package/dist/build-components/OnboardFooter/OnboardFooterProps.generated.d.ts +11 -5
- package/dist/build-components/OnboardImage/OnboardImageProps.generated.d.ts +32 -3
- package/dist/build-components/OnboardItem/OnboardItemProps.generated.d.ts +31 -3
- package/dist/build-components/OnboardProvider/OnboardProviderProps.generated.d.ts +32 -5
- package/dist/build-components/OnboardSubtitle/OnboardSubtitleProps.generated.d.ts +11 -5
- package/dist/build-components/OnboardTitle/OnboardTitleProps.generated.d.ts +11 -5
- package/dist/build-components/Text/TextProps.generated.d.ts +11 -5
- package/dist/build-components/View/ViewProps.generated.d.ts +10 -4
- package/dist/build-components/index.d.ts +2 -1
- package/dist/build-components/patterns.generated.d.ts +6288 -136
- package/dist/components/AttributesEditorPanel.d.ts +3 -4
- package/dist/components/Breadcrumb.d.ts +3 -1
- package/dist/components/Builder.d.ts +2 -1
- package/dist/components/BuilderButton.d.ts +9 -0
- package/dist/components/Checkbox.d.ts +17 -0
- package/dist/components/DeviceButton.d.ts +8 -0
- package/dist/components/DeviceNavigationBar.d.ts +10 -0
- package/dist/components/DeviceStatusBar.d.ts +9 -0
- package/dist/components/EditorHeader.d.ts +3 -8
- package/dist/index.cjs.js +5 -5
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.esm.js +5 -5
- package/dist/index.esm.js.map +1 -1
- package/dist/mockOS/components/MockLaunchScreenComponent.d.ts +6 -0
- package/dist/mockOS/components/MockOSRouter.d.ts +8 -0
- package/dist/mockOS/components/PermissionModal.d.ts +9 -0
- package/dist/mockOS/context/MockOSContext.d.ts +36 -0
- package/dist/mockOS/hooks/useMockNavigation.d.ts +3 -0
- package/dist/mockOS/hooks/useMockPermission.d.ts +3 -0
- package/dist/mockOS/index.d.ts +9 -0
- package/dist/mockOS/managers/mockPermissionManager.d.ts +10 -0
- package/dist/mockOS/managers/navigationManager.d.ts +17 -0
- package/dist/modals/AddComponentModal.d.ts +8 -0
- package/dist/modals/ColorModal.d.ts +11 -0
- package/dist/modals/DeviceSelectorModal.d.ts +9 -0
- package/dist/modals/LocalicationModal.d.ts +8 -0
- package/dist/modals/Modal.d.ts +12 -0
- package/dist/modals/index.d.ts +5 -0
- package/dist/pages/ProjectPage.d.ts +3 -3
- package/dist/pages/tabs/BuilderPanel.d.ts +8 -0
- package/dist/pages/tabs/{DebugTab.d.ts → SideTool.d.ts} +2 -2
- package/dist/store.d.ts +7 -3
- package/dist/styles.css +1 -1
- package/dist/types/Project.d.ts +11 -0
- package/dist/utils/analyseNode.d.ts +1 -0
- package/dist/utils/extractTextStyle.d.ts +8 -1
- package/dist/utils/extractViewStyle.d.ts +8 -1
- package/dist/utils/parseColor.d.ts +7 -0
- package/dist/utils/patterns.d.ts +24 -0
- package/package.json +2 -1
- package/scripts/prebuild/utils/createGeneratedProps.js +11 -3
- package/scripts/prebuild/utils/validateAllComponentsOrThrow.js +45 -6
- package/scripts/prebuild/utils/validatePatternJson.js +13 -5
- package/src/AttributesEditor.tsx +493 -310
- package/src/DeviceMockFrame.tsx +21 -37
- package/src/RenderPage.tsx +86 -7
- package/src/assets/images/android.svg +42 -42
- package/src/assets/images/apple.svg +15 -15
- package/src/attributes-editor/Field.tsx +669 -0
- package/src/attributes-editor/FieldInfoTooltip.tsx +49 -0
- package/src/attributes-editor/LayoutPreviewPicker.tsx +199 -0
- package/src/attributes-editor/SpecialCategorySection.tsx +285 -0
- package/src/attributes-editor/types.ts +30 -0
- package/src/build-components/BackgroundImage/BackgroundImage.tsx +87 -0
- package/src/build-components/BackgroundImage/BackgroundImageProps.generated.ts +60 -0
- package/src/build-components/BackgroundImage/pattern.json +45 -0
- package/src/build-components/Button/Button.tsx +37 -2
- package/src/build-components/Button/ButtonProps.generated.ts +44 -1
- package/src/build-components/Button/pattern.json +31 -2
- package/src/build-components/Carousel/Carousel.tsx +39 -2
- package/src/build-components/Carousel/CarouselProps.generated.ts +46 -1
- package/src/build-components/Carousel/pattern.json +10 -0
- package/src/build-components/CarouselButtons/CarouselButtons.tsx +21 -2
- package/src/build-components/CarouselButtons/CarouselButtonsProps.generated.ts +43 -0
- package/src/build-components/CarouselButtons/pattern.json +22 -0
- package/src/build-components/CarouselDots/CarouselDots.tsx +49 -8
- package/src/build-components/CarouselDots/CarouselDotsProps.generated.ts +43 -0
- package/src/build-components/CarouselDots/pattern.json +15 -0
- package/src/build-components/CarouselItem/CarouselItem.tsx +21 -2
- package/src/build-components/CarouselItem/CarouselItemProps.generated.ts +46 -1
- package/src/build-components/CarouselItem/pattern.json +7 -0
- package/src/build-components/CarouselProvider/CarouselProvider.tsx +21 -2
- package/src/build-components/CarouselProvider/CarouselProviderProps.generated.ts +46 -1
- package/src/build-components/CarouselProvider/pattern.json +7 -0
- package/src/build-components/Image/Image.tsx +33 -2
- package/src/build-components/Image/ImageProps.generated.ts +43 -3
- package/src/build-components/Image/pattern.json +46 -3
- package/src/build-components/Onboard/Onboard.tsx +6 -1
- package/src/build-components/Onboard/OnboardProps.generated.ts +46 -1
- package/src/build-components/Onboard/pattern.json +11 -0
- package/src/build-components/OnboardButton/OnboardButton.tsx +54 -6
- package/src/build-components/OnboardButton/OnboardButtonProps.generated.ts +43 -0
- package/src/build-components/OnboardButton/pattern.json +71 -5
- package/src/build-components/OnboardButtons/OnboardButtons.tsx +33 -11
- package/src/build-components/OnboardButtons/OnboardButtonsProps.generated.ts +43 -0
- package/src/build-components/OnboardButtons/pattern.json +70 -4
- package/src/build-components/OnboardDot/OnboardDot.tsx +113 -4
- package/src/build-components/OnboardDot/OnboardDotProps.generated.ts +29 -0
- package/src/build-components/OnboardDot/pattern.json +55 -2
- package/src/build-components/OnboardFooter/OnboardFooter.tsx +20 -4
- package/src/build-components/OnboardFooter/OnboardFooterProps.generated.ts +11 -5
- package/src/build-components/OnboardFooter/pattern.json +58 -2
- package/src/build-components/OnboardImage/OnboardImage.tsx +49 -5
- package/src/build-components/OnboardImage/OnboardImageProps.generated.ts +43 -3
- package/src/build-components/OnboardImage/pattern.json +21 -0
- package/src/build-components/OnboardItem/OnboardItem.tsx +17 -1
- package/src/build-components/OnboardItem/OnboardItemProps.generated.ts +42 -3
- package/src/build-components/OnboardItem/pattern.json +38 -2
- package/src/build-components/OnboardProvider/OnboardProvider.tsx +52 -18
- package/src/build-components/OnboardProvider/OnboardProviderProps.generated.ts +44 -5
- package/src/build-components/OnboardProvider/pattern.json +44 -5
- package/src/build-components/OnboardSubtitle/OnboardSubtitleProps.generated.ts +11 -5
- package/src/build-components/OnboardSubtitle/pattern.json +7 -1
- package/src/build-components/OnboardTitle/OnboardTitleProps.generated.ts +11 -5
- package/src/build-components/OnboardTitle/pattern.json +7 -1
- package/src/build-components/RenderNode.generated.tsx +3 -0
- package/src/build-components/Text/Text.tsx +34 -6
- package/src/build-components/Text/TextProps.generated.ts +11 -5
- package/src/build-components/Text/pattern.json +38 -2
- package/src/build-components/View/View.tsx +33 -6
- package/src/build-components/View/ViewProps.generated.ts +10 -4
- package/src/build-components/View/pattern.json +285 -19
- package/src/build-components/index.ts +5 -0
- package/src/build-components/patterns.generated.ts +6346 -143
- package/src/components/AttributesEditorPanel.tsx +17 -64
- package/src/components/Breadcrumb.tsx +37 -5
- package/src/components/Builder.tsx +311 -108
- package/src/components/BuilderButton.tsx +127 -0
- package/src/components/Checkbox.tsx +81 -0
- package/src/components/DeviceButton.tsx +39 -0
- package/src/components/DeviceNavigationBar.tsx +201 -0
- package/src/components/DeviceStatusBar.tsx +85 -0
- package/src/components/EditorHeader.tsx +26 -74
- package/src/index.ts +2 -2
- package/src/mockOS/components/MockLaunchScreenComponent.tsx +43 -0
- package/src/mockOS/components/MockOSRouter.tsx +123 -0
- package/src/mockOS/components/PermissionModal.tsx +270 -0
- package/src/mockOS/context/MockOSContext.tsx +179 -0
- package/src/mockOS/hooks/useMockNavigation.ts +11 -0
- package/src/mockOS/hooks/useMockPermission.ts +11 -0
- package/src/mockOS/index.ts +26 -0
- package/src/mockOS/managers/mockPermissionManager.ts +54 -0
- package/src/mockOS/managers/navigationManager.ts +91 -0
- package/src/modals/AddComponentModal.tsx +313 -0
- package/src/modals/ColorModal.tsx +425 -0
- package/src/modals/DeviceSelectorModal.tsx +57 -0
- package/src/modals/LocalicationModal.tsx +54 -0
- package/src/modals/Modal.tsx +57 -0
- package/src/modals/index.ts +5 -0
- package/src/pages/ProjectPage.tsx +307 -71
- package/src/pages/tabs/{BuilderTab.tsx → BuilderPanel.tsx} +13 -9
- package/src/pages/tabs/SideTool.tsx +259 -0
- package/src/size-matters/index.ts +27 -5
- package/src/store.ts +13 -5
- package/src/styles/base/_global.scss +404 -0
- package/src/styles/components/_attributes-editor.scss +273 -0
- package/src/styles/components/_editor-shell.scss +212 -0
- package/src/styles/components/_mockos-router.scss +140 -0
- package/src/styles/components/_ui-components.scss +183 -0
- package/src/styles/foundation/_colors.scss +8 -0
- package/src/styles/{_mixins.scss → foundation/_mixins.scss} +5 -4
- package/src/styles/{_reset.scss → foundation/_reset.scss} +5 -2
- package/src/styles/foundation/_sizes.scss +37 -0
- package/src/styles/foundation/_typography.scss +4 -0
- package/src/styles/foundation/_variables.scss +3 -0
- package/src/styles/index.scss +22 -136
- package/src/styles/layout/_builder.scss +124 -0
- package/src/styles/layout/_pages.scss +3 -0
- package/src/styles/modals/_add-component.scss +122 -0
- package/src/styles/modals/_color-modal.scss +159 -0
- package/src/styles/modals/_device-selector.scss +18 -0
- package/src/styles/modals/_localication-modal.scss +68 -0
- package/src/styles/modals/_modal-shell.scss +46 -0
- package/src/styles/utilities/_carousel.scss +125 -0
- package/src/types/Project.ts +14 -0
- package/src/types/images.d.ts +8 -0
- package/src/utils/analyseNode.ts +98 -0
- package/src/utils/extractTextStyle.ts +28 -10
- package/src/utils/extractViewStyle.ts +77 -9
- package/src/utils/parseColor.ts +43 -0
- package/src/utils/patterns.ts +33 -0
- package/dist/build-components/OnboardDot/OnboardExpandingDotProps.generated.d.ts +0 -10
- package/dist/pages/tabs/BuilderTab.d.ts +0 -9
- package/dist/pages/tabs/PreviewTab.d.ts +0 -3
- package/src/build-components/OnboardDot/OnboardExpandingDotProps.generated.ts +0 -20
- package/src/pages/tabs/DebugTab.tsx +0 -23
- package/src/pages/tabs/PreviewTab.tsx +0 -194
- package/src/styles/_variables.scss +0 -27
- package/src/styles/builder.scss +0 -60
- package/src/styles/components.scss +0 -88
- package/src/styles/editor.scss +0 -174
- package/src/styles/global.scss +0 -200
- package/src/styles/pages.scss +0 -2
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { MockOSContextValue } from '../context/MockOSContext';
|
|
2
|
+
|
|
3
|
+
export type PermissionType =
|
|
4
|
+
| 'camera'
|
|
5
|
+
| 'microphone'
|
|
6
|
+
| 'location'
|
|
7
|
+
| 'notifications'
|
|
8
|
+
| 'photos'
|
|
9
|
+
| 'contacts'
|
|
10
|
+
| 'att'; // App Tracking Transparency
|
|
11
|
+
|
|
12
|
+
export type PermissionStatus = 'granted' | 'denied' | 'not-determined';
|
|
13
|
+
|
|
14
|
+
export class MockPermissionManager {
|
|
15
|
+
private context: MockOSContextValue | null;
|
|
16
|
+
|
|
17
|
+
constructor(context: MockOSContextValue | null) {
|
|
18
|
+
this.context = context;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
requestPermission(permission: PermissionType | string): PermissionStatus {
|
|
22
|
+
if (this.context === null) {
|
|
23
|
+
alert(
|
|
24
|
+
`Permission requested: ${permission}\n(Mock OS context not available)`,
|
|
25
|
+
);
|
|
26
|
+
return 'not-determined';
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
console.log(`[Mock OS] Permission requested: ${permission}`);
|
|
30
|
+
// Set permission to trigger modal display
|
|
31
|
+
this.context.setPermission(permission);
|
|
32
|
+
// Default behavior: grant all permissions in mock environment
|
|
33
|
+
return 'granted';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
checkPermission(permission: PermissionType | string): PermissionStatus {
|
|
37
|
+
if (this.context === null) {
|
|
38
|
+
alert(`Permission check: ${permission}\n(Mock OS context not available)`);
|
|
39
|
+
return 'not-determined';
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
console.log(`[Mock OS] Permission checked: ${permission}`);
|
|
43
|
+
return 'granted';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
openSettings(): void {
|
|
47
|
+
if (this.context === null) {
|
|
48
|
+
alert('Opening Settings\n(Mock OS context not available)');
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
console.log('[Mock OS] Opening Settings');
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { MockOSContextValue } from '../context/MockOSContext';
|
|
2
|
+
|
|
3
|
+
export type NavigationDestination =
|
|
4
|
+
| 'home'
|
|
5
|
+
| 'subscriptions'
|
|
6
|
+
| 'launchApp'
|
|
7
|
+
| 'settings'
|
|
8
|
+
| 'profile';
|
|
9
|
+
|
|
10
|
+
export interface NavigationStackItem {
|
|
11
|
+
destination: NavigationDestination;
|
|
12
|
+
timestamp: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export class MockNavigationManager {
|
|
16
|
+
private context: MockOSContextValue | null;
|
|
17
|
+
private stack: NavigationStackItem[] = [];
|
|
18
|
+
|
|
19
|
+
constructor(context: MockOSContextValue | null) {
|
|
20
|
+
this.context = context;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
goToHome(): void {
|
|
24
|
+
if (this.context === null) {
|
|
25
|
+
alert('Navigate to Home\n(Mock OS context not available)');
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const item: NavigationStackItem = {
|
|
30
|
+
destination: 'home',
|
|
31
|
+
timestamp: Date.now(),
|
|
32
|
+
};
|
|
33
|
+
this.stack.push(item);
|
|
34
|
+
console.log('[Mock OS] Navigate to Home', { stack: this.stack });
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
goToSubscriptions(): void {
|
|
38
|
+
if (this.context === null) {
|
|
39
|
+
alert('Navigate to Subscriptions\n(Mock OS context not available)');
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const item: NavigationStackItem = {
|
|
44
|
+
destination: 'subscriptions',
|
|
45
|
+
timestamp: Date.now(),
|
|
46
|
+
};
|
|
47
|
+
this.stack.push(item);
|
|
48
|
+
console.log('[Mock OS] Navigate to Subscriptions', { stack: this.stack });
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
goToLaunchApp(): void {
|
|
52
|
+
if (this.context === null) {
|
|
53
|
+
alert('Navigate to Launch App\n(Mock OS context not available)');
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const item: NavigationStackItem = {
|
|
58
|
+
destination: 'launchApp',
|
|
59
|
+
timestamp: Date.now(),
|
|
60
|
+
};
|
|
61
|
+
this.stack.push(item);
|
|
62
|
+
console.log('[Mock OS] Navigate to Launch App', { stack: this.stack });
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
goBack(): boolean {
|
|
66
|
+
if (this.context === null) {
|
|
67
|
+
alert('Go Back\n(Mock OS context not available)');
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (this.stack.length > 0) {
|
|
72
|
+
const popped = this.stack.pop();
|
|
73
|
+
console.log('[Mock OS] Go Back', { popped, stack: this.stack });
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
console.log('[Mock OS] Cannot go back - stack is empty');
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
getStack(): NavigationStackItem[] {
|
|
82
|
+
return [...this.stack];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
clearStack(): void {
|
|
86
|
+
this.stack = [];
|
|
87
|
+
if (this.context) {
|
|
88
|
+
console.log('[Mock OS] Navigation stack cleared');
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
import React, { useEffect, useMemo, useState } from 'react';
|
|
2
|
+
import Modal from './Modal';
|
|
3
|
+
import { getPatternByType } from '../utils/patterns';
|
|
4
|
+
|
|
5
|
+
type AddComponentModalProps = {
|
|
6
|
+
allowedChildTypes: string[];
|
|
7
|
+
parentType?: string | null;
|
|
8
|
+
onSelect: (type: string) => void;
|
|
9
|
+
onClose: () => void;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
type ComponentOption = {
|
|
13
|
+
type: string;
|
|
14
|
+
label: string;
|
|
15
|
+
desiredParents: string[];
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
type ComponentGroup = {
|
|
19
|
+
token: string;
|
|
20
|
+
displayLabel: string;
|
|
21
|
+
options: ComponentOption[];
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const FALLBACK_PARENT = 'all';
|
|
25
|
+
const EXCLUDE_PREFIX = '!=';
|
|
26
|
+
|
|
27
|
+
function normalizeDesiredParents(list?: string[]): string[] {
|
|
28
|
+
if (!Array.isArray(list) || list.length === 0) {
|
|
29
|
+
return [FALLBACK_PARENT];
|
|
30
|
+
}
|
|
31
|
+
return list;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function matchesDesiredChildToken(token: string, childType: string): boolean {
|
|
35
|
+
if (token.startsWith('=')) return token.slice(1) === childType;
|
|
36
|
+
if (token.startsWith('>')) return token.slice(1) === childType;
|
|
37
|
+
return token === childType;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function filterOptionsByDesiredChildren(
|
|
41
|
+
options: ComponentOption[],
|
|
42
|
+
desiredChildren?: string[],
|
|
43
|
+
): ComponentOption[] {
|
|
44
|
+
if (!Array.isArray(desiredChildren) || desiredChildren.length === 0) {
|
|
45
|
+
return options;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const tokens = desiredChildren
|
|
49
|
+
.map((token) => token.trim())
|
|
50
|
+
.filter((token) => token.length > 0);
|
|
51
|
+
|
|
52
|
+
if (tokens.length === 0) {
|
|
53
|
+
return options;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return options.filter(({ type }) => {
|
|
57
|
+
let hasPositiveRule = false;
|
|
58
|
+
let matchesPositiveRule = false;
|
|
59
|
+
|
|
60
|
+
for (const token of tokens) {
|
|
61
|
+
if (token.startsWith(EXCLUDE_PREFIX)) {
|
|
62
|
+
if (type === token.slice(EXCLUDE_PREFIX.length)) {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (token === FALLBACK_PARENT) {
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
hasPositiveRule = true;
|
|
73
|
+
if (matchesDesiredChildToken(token, type)) {
|
|
74
|
+
matchesPositiveRule = true;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (!hasPositiveRule) {
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return matchesPositiveRule;
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function formatTokenLabel(token: string): string {
|
|
87
|
+
if (token === 'all') return 'Anywhere';
|
|
88
|
+
if (token === 'root') return 'Root only';
|
|
89
|
+
if (token.startsWith('!=')) return `Not under ${token.slice(2)}`;
|
|
90
|
+
if (token.startsWith('=')) return `Direct child of ${token.slice(1)}`;
|
|
91
|
+
if (token.startsWith('>')) return `Inside ${token.slice(1)}`;
|
|
92
|
+
return token;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function tokenMatchesParent(
|
|
96
|
+
token: string,
|
|
97
|
+
parentType?: string | null,
|
|
98
|
+
): boolean {
|
|
99
|
+
if (token === 'all') return true;
|
|
100
|
+
if (!parentType || parentType.length === 0) {
|
|
101
|
+
return token === 'root';
|
|
102
|
+
}
|
|
103
|
+
if (token.startsWith('!=')) return false;
|
|
104
|
+
if (token.startsWith('=')) return token.slice(1) === parentType;
|
|
105
|
+
if (token.startsWith('>')) return token.slice(1) === parentType;
|
|
106
|
+
return token === parentType;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function buildGroups(
|
|
110
|
+
options: ComponentOption[],
|
|
111
|
+
tokenFilter?: (token: string, option: ComponentOption) => boolean,
|
|
112
|
+
): ComponentGroup[] {
|
|
113
|
+
const map = new Map<string, ComponentOption[]>();
|
|
114
|
+
|
|
115
|
+
options.forEach((option) => {
|
|
116
|
+
option.desiredParents.forEach((token) => {
|
|
117
|
+
if (tokenFilter && !tokenFilter(token, option)) return;
|
|
118
|
+
const existing = map.get(token);
|
|
119
|
+
if (existing) {
|
|
120
|
+
if (!existing.some((item) => item.type === option.type)) {
|
|
121
|
+
existing.push(option);
|
|
122
|
+
}
|
|
123
|
+
} else {
|
|
124
|
+
map.set(token, [option]);
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
return Array.from(map.entries())
|
|
130
|
+
.map(([token, bucket]) => ({
|
|
131
|
+
token,
|
|
132
|
+
displayLabel: formatTokenLabel(token),
|
|
133
|
+
options: bucket.sort((a, b) => a.label.localeCompare(b.label)),
|
|
134
|
+
}))
|
|
135
|
+
.sort((a, b) => a.displayLabel.localeCompare(b.displayLabel));
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function AddComponentModal({
|
|
139
|
+
allowedChildTypes,
|
|
140
|
+
parentType,
|
|
141
|
+
onSelect,
|
|
142
|
+
onClose,
|
|
143
|
+
}: AddComponentModalProps) {
|
|
144
|
+
const [showAllComponents, setShowAllComponents] = useState(false);
|
|
145
|
+
|
|
146
|
+
const componentOptions = useMemo<ComponentOption[]>(() => {
|
|
147
|
+
const uniqueTypes = Array.from(new Set(allowedChildTypes));
|
|
148
|
+
return uniqueTypes
|
|
149
|
+
.map((type) => {
|
|
150
|
+
const pattern = getPatternByType(type);
|
|
151
|
+
const label = pattern?.meta?.label?.trim() || type;
|
|
152
|
+
const desiredParents = normalizeDesiredParents(
|
|
153
|
+
pattern?.meta?.desiredParent,
|
|
154
|
+
);
|
|
155
|
+
return {
|
|
156
|
+
type,
|
|
157
|
+
label,
|
|
158
|
+
desiredParents,
|
|
159
|
+
};
|
|
160
|
+
})
|
|
161
|
+
.sort((a, b) => a.label.localeCompare(b.label));
|
|
162
|
+
}, [allowedChildTypes]);
|
|
163
|
+
|
|
164
|
+
const parentDesiredChildren = useMemo<string[] | undefined>(() => {
|
|
165
|
+
if (!parentType) return undefined;
|
|
166
|
+
const desiredChildren = getPatternByType(parentType)?.meta?.desiredChildren;
|
|
167
|
+
if (!Array.isArray(desiredChildren) || desiredChildren.length === 0) {
|
|
168
|
+
return undefined;
|
|
169
|
+
}
|
|
170
|
+
return desiredChildren
|
|
171
|
+
.map((token) => token.trim())
|
|
172
|
+
.filter((token) => token.length > 0);
|
|
173
|
+
}, [parentType]);
|
|
174
|
+
|
|
175
|
+
const filteredComponentOptions = useMemo<ComponentOption[]>(
|
|
176
|
+
() =>
|
|
177
|
+
filterOptionsByDesiredChildren(componentOptions, parentDesiredChildren),
|
|
178
|
+
[componentOptions, parentDesiredChildren],
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
const canToggleAllComponents = !parentDesiredChildren?.length;
|
|
182
|
+
|
|
183
|
+
useEffect(() => {
|
|
184
|
+
if (!canToggleAllComponents) {
|
|
185
|
+
setShowAllComponents(false);
|
|
186
|
+
}
|
|
187
|
+
}, [canToggleAllComponents]);
|
|
188
|
+
|
|
189
|
+
const recommendedGroups = useMemo<ComponentGroup[]>(
|
|
190
|
+
() =>
|
|
191
|
+
buildGroups(filteredComponentOptions, (token) =>
|
|
192
|
+
tokenMatchesParent(token, parentType),
|
|
193
|
+
),
|
|
194
|
+
[filteredComponentOptions, parentType],
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
const allGroups = useMemo<ComponentGroup[]>(
|
|
198
|
+
() => buildGroups(filteredComponentOptions),
|
|
199
|
+
[filteredComponentOptions],
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
const handleSelect = (type: string) => {
|
|
203
|
+
onSelect(type);
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
const renderOption = (option: ComponentOption) => (
|
|
207
|
+
<button
|
|
208
|
+
key={option.type}
|
|
209
|
+
type="button"
|
|
210
|
+
className="add-component-modal__card"
|
|
211
|
+
onClick={() => handleSelect(option.type)}
|
|
212
|
+
role="listitem"
|
|
213
|
+
>
|
|
214
|
+
<div className="add-component-modal__thumbnail" aria-hidden="true">
|
|
215
|
+
<span>Preview</span>
|
|
216
|
+
</div>
|
|
217
|
+
<span className="add-component-modal__title">{option.label}</span>
|
|
218
|
+
</button>
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
const renderGroups = (groups: ComponentGroup[]) =>
|
|
222
|
+
groups.map((group) => (
|
|
223
|
+
<div key={group.token} className="add-component-modal__group">
|
|
224
|
+
<h5 className="add-component-modal__group-title">
|
|
225
|
+
{group.displayLabel}
|
|
226
|
+
</h5>
|
|
227
|
+
<div className="add-component-modal__grid" role="list">
|
|
228
|
+
{group.options.map(renderOption)}
|
|
229
|
+
</div>
|
|
230
|
+
</div>
|
|
231
|
+
));
|
|
232
|
+
|
|
233
|
+
const hasRecommended = recommendedGroups.length > 0;
|
|
234
|
+
const hasComponents = filteredComponentOptions.length > 0;
|
|
235
|
+
|
|
236
|
+
return (
|
|
237
|
+
<Modal
|
|
238
|
+
onClose={onClose}
|
|
239
|
+
ariaLabelledBy="add-component-title"
|
|
240
|
+
contentClassName="add-component-modal"
|
|
241
|
+
>
|
|
242
|
+
<div className="modal__header">
|
|
243
|
+
<h3 id="add-component-title" className="modal__title">
|
|
244
|
+
Add component
|
|
245
|
+
</h3>
|
|
246
|
+
<button
|
|
247
|
+
type="button"
|
|
248
|
+
className="editor-button"
|
|
249
|
+
aria-label="Close add component modal"
|
|
250
|
+
onClick={onClose}
|
|
251
|
+
>
|
|
252
|
+
Close
|
|
253
|
+
</button>
|
|
254
|
+
</div>
|
|
255
|
+
|
|
256
|
+
<div className="add-component-modal__body">
|
|
257
|
+
<section className="add-component-modal__section">
|
|
258
|
+
<h4 className="add-component-modal__section-title">
|
|
259
|
+
Recommended Components
|
|
260
|
+
</h4>
|
|
261
|
+
{hasRecommended ? (
|
|
262
|
+
renderGroups(recommendedGroups)
|
|
263
|
+
) : (
|
|
264
|
+
<p className="add-component-modal__empty">
|
|
265
|
+
{hasComponents
|
|
266
|
+
? 'No direct recommendations for this parent.'
|
|
267
|
+
: 'No components available for this node.'}
|
|
268
|
+
</p>
|
|
269
|
+
)}
|
|
270
|
+
</section>
|
|
271
|
+
|
|
272
|
+
{canToggleAllComponents && (
|
|
273
|
+
<label className="add-component-modal__toggle">
|
|
274
|
+
<input
|
|
275
|
+
type="checkbox"
|
|
276
|
+
checked={showAllComponents}
|
|
277
|
+
onChange={(event) => setShowAllComponents(event.target.checked)}
|
|
278
|
+
/>
|
|
279
|
+
<span>Show rest (All Components)</span>
|
|
280
|
+
</label>
|
|
281
|
+
)}
|
|
282
|
+
|
|
283
|
+
{canToggleAllComponents && showAllComponents && (
|
|
284
|
+
<section
|
|
285
|
+
className="add-component-modal__section"
|
|
286
|
+
aria-live="polite"
|
|
287
|
+
id="add-component-modal-all"
|
|
288
|
+
>
|
|
289
|
+
<h4 className="add-component-modal__section-title">
|
|
290
|
+
All Components
|
|
291
|
+
</h4>
|
|
292
|
+
{allGroups.length ? (
|
|
293
|
+
renderGroups(allGroups)
|
|
294
|
+
) : (
|
|
295
|
+
<p className="add-component-modal__empty">
|
|
296
|
+
No components available.
|
|
297
|
+
</p>
|
|
298
|
+
)}
|
|
299
|
+
</section>
|
|
300
|
+
)}
|
|
301
|
+
{!showAllComponents && !hasRecommended && (
|
|
302
|
+
<p className="add-component-modal__empty">
|
|
303
|
+
{canToggleAllComponents
|
|
304
|
+
? 'Nothing to suggest yet—toggle “Show rest” to browse all components.'
|
|
305
|
+
: 'Nothing to suggest yet for this parent.'}
|
|
306
|
+
</p>
|
|
307
|
+
)}
|
|
308
|
+
</div>
|
|
309
|
+
</Modal>
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
export default AddComponentModal;
|