@developer_tribe/react-builder 1.0.1 → 1.0.2
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/DeviceMockFrame.d.ts +2 -1
- package/dist/RenderPage.d.ts +4 -3
- package/dist/attributes-editor/Field.d.ts +16 -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 +19 -0
- package/dist/attributes-editor/types.d.ts +14 -0
- package/dist/background.jpg +0 -0
- package/dist/build-components/Button/Button.d.ts +1 -1
- package/dist/build-components/Button/ButtonProps.generated.d.ts +26 -1
- package/dist/build-components/Carousel/CarouselProps.generated.d.ts +27 -1
- package/dist/build-components/CarouselButtons/CarouselButtonsProps.generated.d.ts +25 -0
- package/dist/build-components/CarouselDots/CarouselDotsProps.generated.d.ts +25 -0
- package/dist/build-components/CarouselItem/CarouselItemProps.generated.d.ts +27 -1
- package/dist/build-components/CarouselProvider/CarouselProviderProps.generated.d.ts +27 -1
- package/dist/build-components/Image/ImageProps.generated.d.ts +25 -3
- package/dist/build-components/Onboard/OnboardProps.generated.d.ts +27 -1
- package/dist/build-components/OnboardButton/OnboardButtonProps.generated.d.ts +25 -0
- package/dist/build-components/OnboardButtons/OnboardButtonsProps.generated.d.ts +25 -0
- package/dist/build-components/OnboardDot/OnboardDot.d.ts +1 -1
- package/dist/build-components/OnboardDot/OnboardDotProps.generated.d.ts +22 -0
- package/dist/build-components/OnboardFooter/OnboardFooterProps.generated.d.ts +4 -5
- package/dist/build-components/OnboardImage/OnboardImageProps.generated.d.ts +25 -3
- package/dist/build-components/OnboardItem/OnboardItemProps.generated.d.ts +24 -3
- package/dist/build-components/OnboardProvider/OnboardProviderProps.generated.d.ts +25 -4
- package/dist/build-components/OnboardSubtitle/OnboardSubtitleProps.generated.d.ts +4 -5
- package/dist/build-components/OnboardTitle/OnboardTitleProps.generated.d.ts +4 -5
- package/dist/build-components/Text/TextProps.generated.d.ts +4 -5
- package/dist/build-components/View/ViewProps.generated.d.ts +3 -4
- package/dist/build-components/patterns.generated.d.ts +4855 -132
- package/dist/components/Breadcrumb.d.ts +3 -1
- 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.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 +9 -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 +1 -1
- package/dist/store.d.ts +0 -2
- package/dist/styles.css +1 -1
- 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 +433 -312
- package/src/DeviceMockFrame.tsx +21 -37
- package/src/RenderPage.tsx +5 -4
- package/src/assets/images/android.svg +42 -42
- package/src/assets/images/apple.svg +15 -15
- package/src/attributes-editor/Field.tsx +662 -0
- package/src/attributes-editor/FieldInfoTooltip.tsx +49 -0
- package/src/attributes-editor/LayoutPreviewPicker.tsx +199 -0
- package/src/attributes-editor/SpecialCategorySection.tsx +284 -0
- package/src/attributes-editor/types.ts +30 -0
- package/src/build-components/Button/Button.tsx +10 -2
- package/src/build-components/Button/ButtonProps.generated.ts +37 -1
- package/src/build-components/Button/pattern.json +31 -2
- package/src/build-components/Carousel/Carousel.tsx +15 -2
- package/src/build-components/Carousel/CarouselProps.generated.ts +39 -1
- package/src/build-components/Carousel/pattern.json +10 -0
- package/src/build-components/CarouselButtons/CarouselButtons.tsx +6 -2
- package/src/build-components/CarouselButtons/CarouselButtonsProps.generated.ts +36 -0
- package/src/build-components/CarouselButtons/pattern.json +22 -0
- package/src/build-components/CarouselDots/CarouselDots.tsx +40 -8
- package/src/build-components/CarouselDots/CarouselDotsProps.generated.ts +36 -0
- package/src/build-components/CarouselDots/pattern.json +15 -0
- package/src/build-components/CarouselItem/CarouselItem.tsx +5 -2
- package/src/build-components/CarouselItem/CarouselItemProps.generated.ts +39 -1
- package/src/build-components/CarouselItem/pattern.json +7 -0
- package/src/build-components/CarouselProvider/CarouselProvider.tsx +10 -2
- package/src/build-components/CarouselProvider/CarouselProviderProps.generated.ts +39 -1
- package/src/build-components/CarouselProvider/pattern.json +7 -0
- package/src/build-components/Image/Image.tsx +8 -2
- package/src/build-components/Image/ImageProps.generated.ts +36 -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 +39 -1
- package/src/build-components/Onboard/pattern.json +11 -0
- package/src/build-components/OnboardButton/OnboardButton.tsx +46 -5
- package/src/build-components/OnboardButton/OnboardButtonProps.generated.ts +36 -0
- package/src/build-components/OnboardButton/pattern.json +71 -5
- package/src/build-components/OnboardButtons/OnboardButtons.tsx +20 -10
- package/src/build-components/OnboardButtons/OnboardButtonsProps.generated.ts +36 -0
- package/src/build-components/OnboardButtons/pattern.json +70 -4
- package/src/build-components/OnboardDot/OnboardDot.tsx +104 -4
- package/src/build-components/OnboardDot/OnboardDotProps.generated.ts +22 -0
- package/src/build-components/OnboardDot/pattern.json +54 -1
- package/src/build-components/OnboardFooter/OnboardFooter.tsx +9 -3
- package/src/build-components/OnboardFooter/OnboardFooterProps.generated.ts +4 -5
- package/src/build-components/OnboardFooter/pattern.json +58 -2
- package/src/build-components/OnboardImage/OnboardImage.tsx +27 -5
- package/src/build-components/OnboardImage/OnboardImageProps.generated.ts +36 -3
- package/src/build-components/OnboardImage/pattern.json +21 -0
- package/src/build-components/OnboardItem/OnboardItem.tsx +6 -1
- package/src/build-components/OnboardItem/OnboardItemProps.generated.ts +35 -3
- package/src/build-components/OnboardItem/pattern.json +38 -2
- package/src/build-components/OnboardProvider/OnboardProvider.tsx +20 -8
- package/src/build-components/OnboardProvider/OnboardProviderProps.generated.ts +37 -4
- package/src/build-components/OnboardProvider/pattern.json +51 -4
- package/src/build-components/OnboardSubtitle/OnboardSubtitleProps.generated.ts +4 -5
- package/src/build-components/OnboardSubtitle/pattern.json +6 -0
- package/src/build-components/OnboardTitle/OnboardTitleProps.generated.ts +4 -5
- package/src/build-components/OnboardTitle/pattern.json +6 -0
- package/src/build-components/Text/Text.tsx +7 -3
- package/src/build-components/Text/TextProps.generated.ts +4 -5
- package/src/build-components/Text/pattern.json +38 -2
- package/src/build-components/View/View.tsx +9 -6
- package/src/build-components/View/ViewProps.generated.ts +3 -4
- package/src/build-components/View/pattern.json +227 -19
- package/src/build-components/patterns.generated.ts +4905 -139
- package/src/components/AttributesEditorPanel.tsx +7 -61
- package/src/components/Breadcrumb.tsx +37 -5
- package/src/components/Builder.tsx +180 -77
- 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/mockOS/components/MockLaunchScreenComponent.tsx +43 -0
- package/src/mockOS/components/MockOSRouter.tsx +115 -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 +268 -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 +19 -21
- package/src/pages/tabs/DebugTab.tsx +50 -9
- package/src/pages/tabs/PreviewTab.tsx +52 -40
- package/src/size-matters/index.ts +21 -5
- package/src/store.ts +0 -4
- package/src/styles/{global.scss → base/_global.scss} +92 -39
- package/src/styles/components/_attributes-editor.scss +261 -0
- package/src/styles/{editor.scss → components/_editor-shell.scss} +72 -57
- 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 +68 -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 +130 -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/images.d.ts +8 -0
- package/src/utils/extractTextStyle.ts +4 -2
- package/src/utils/extractViewStyle.ts +51 -7
- package/src/utils/patterns.ts +33 -0
- package/dist/build-components/OnboardDot/OnboardExpandingDotProps.generated.d.ts +0 -10
- package/src/build-components/OnboardDot/OnboardExpandingDotProps.generated.ts +0 -20
- package/src/styles/_variables.scss +0 -27
- package/src/styles/builder.scss +0 -60
- package/src/styles/components.scss +0 -88
- package/src/styles/pages.scss +0 -2
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { ViewPropsGenerated } from '../build-components/View/ViewProps.generated';
|
|
3
|
+
import { LayoutContext, LayoutFieldName } from './types';
|
|
4
|
+
|
|
5
|
+
type LayoutPreviewPickerProps = {
|
|
6
|
+
mode: LayoutFieldName;
|
|
7
|
+
options: string[];
|
|
8
|
+
value?: string;
|
|
9
|
+
onChange: (val?: string) => void;
|
|
10
|
+
layoutContext?: LayoutContext;
|
|
11
|
+
viewAttributes?: Partial<ViewPropsGenerated['attributes']>;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export function LayoutPreviewPicker({
|
|
15
|
+
mode,
|
|
16
|
+
options,
|
|
17
|
+
value,
|
|
18
|
+
onChange,
|
|
19
|
+
layoutContext,
|
|
20
|
+
viewAttributes,
|
|
21
|
+
}: LayoutPreviewPickerProps) {
|
|
22
|
+
if (!options.length) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const selectedValue =
|
|
27
|
+
typeof value === 'string'
|
|
28
|
+
? value
|
|
29
|
+
: (layoutContext?.[mode as keyof LayoutContext] as string | undefined);
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<div
|
|
33
|
+
style={{
|
|
34
|
+
display: 'flex',
|
|
35
|
+
flexWrap: 'wrap',
|
|
36
|
+
gap: 6,
|
|
37
|
+
paddingBottom: 2,
|
|
38
|
+
}}
|
|
39
|
+
>
|
|
40
|
+
{options.map((option) => {
|
|
41
|
+
const isActive = selectedValue === option;
|
|
42
|
+
return (
|
|
43
|
+
<button
|
|
44
|
+
key={option}
|
|
45
|
+
type="button"
|
|
46
|
+
onClick={() => onChange(option)}
|
|
47
|
+
style={{
|
|
48
|
+
borderRadius: 8,
|
|
49
|
+
padding: 5,
|
|
50
|
+
width: 110,
|
|
51
|
+
flex: '0 0 auto',
|
|
52
|
+
border: isActive ? '2px solid #222' : '1px solid #d5d5d5',
|
|
53
|
+
background: '#fff',
|
|
54
|
+
textAlign: 'left',
|
|
55
|
+
display: 'flex',
|
|
56
|
+
flexDirection: 'column',
|
|
57
|
+
gap: 3,
|
|
58
|
+
cursor: 'pointer',
|
|
59
|
+
boxShadow: isActive ? '0 2px 4px rgba(0,0,0,0.08)' : 'none',
|
|
60
|
+
}}
|
|
61
|
+
>
|
|
62
|
+
<LayoutPreview
|
|
63
|
+
mode={mode}
|
|
64
|
+
option={option}
|
|
65
|
+
isActive={isActive}
|
|
66
|
+
layoutContext={layoutContext}
|
|
67
|
+
viewAttributes={viewAttributes}
|
|
68
|
+
/>
|
|
69
|
+
<span style={{ fontWeight: 600, fontSize: 11 }}>
|
|
70
|
+
{formatLayoutLabel(option)}
|
|
71
|
+
</span>
|
|
72
|
+
</button>
|
|
73
|
+
);
|
|
74
|
+
})}
|
|
75
|
+
</div>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
type LayoutPreviewProps = {
|
|
80
|
+
mode: LayoutFieldName;
|
|
81
|
+
option: string;
|
|
82
|
+
isActive: boolean;
|
|
83
|
+
layoutContext?: LayoutContext;
|
|
84
|
+
viewAttributes?: Partial<ViewPropsGenerated['attributes']>;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
function LayoutPreview({
|
|
88
|
+
mode,
|
|
89
|
+
option,
|
|
90
|
+
isActive,
|
|
91
|
+
layoutContext,
|
|
92
|
+
viewAttributes,
|
|
93
|
+
}: LayoutPreviewProps) {
|
|
94
|
+
const resolved = resolvePreviewContext(mode, option, layoutContext);
|
|
95
|
+
const isColumn = resolved.flexDirection?.startsWith('column');
|
|
96
|
+
const resolvedBackground =
|
|
97
|
+
viewAttributes?.backgroundColor ?? (isActive ? '#fffdf5' : '#fafafa');
|
|
98
|
+
const resolvedGap = parseNumeric(viewAttributes?.gap) ?? 3;
|
|
99
|
+
const resolvedBorderRadius = parseNumeric(viewAttributes?.borderRadius) ?? 4;
|
|
100
|
+
|
|
101
|
+
const stretchOverrides =
|
|
102
|
+
resolved.alignItems === 'stretch'
|
|
103
|
+
? isColumn
|
|
104
|
+
? { width: '100%' }
|
|
105
|
+
: { height: '100%' }
|
|
106
|
+
: undefined;
|
|
107
|
+
|
|
108
|
+
return (
|
|
109
|
+
<div
|
|
110
|
+
style={{
|
|
111
|
+
borderRadius: resolvedBorderRadius,
|
|
112
|
+
border: '1px dashed rgba(0,0,0,0.2)',
|
|
113
|
+
padding: 5,
|
|
114
|
+
background: resolvedBackground,
|
|
115
|
+
}}
|
|
116
|
+
>
|
|
117
|
+
<div
|
|
118
|
+
style={{
|
|
119
|
+
display: 'flex',
|
|
120
|
+
flexDirection: resolved.flexDirection ?? 'row',
|
|
121
|
+
justifyContent: resolved.justifyContent ?? 'flex-start',
|
|
122
|
+
alignItems: resolved.alignItems ?? 'stretch',
|
|
123
|
+
gap: resolvedGap,
|
|
124
|
+
width: '100%',
|
|
125
|
+
minHeight: isColumn ? 52 : 40,
|
|
126
|
+
transition: 'all 0.2s ease',
|
|
127
|
+
}}
|
|
128
|
+
>
|
|
129
|
+
{Array.from({ length: 3 }).map((_, idx) => (
|
|
130
|
+
<span
|
|
131
|
+
key={idx}
|
|
132
|
+
style={{
|
|
133
|
+
width: isColumn ? 22 : 10,
|
|
134
|
+
height: isColumn ? 10 : 10,
|
|
135
|
+
borderRadius: 2.5,
|
|
136
|
+
background: '#f7a500',
|
|
137
|
+
border: '1px solid rgba(0,0,0,0.1)',
|
|
138
|
+
flex: mode === 'justifyContent' ? '0 0 auto' : undefined,
|
|
139
|
+
...(stretchOverrides ?? {}),
|
|
140
|
+
}}
|
|
141
|
+
/>
|
|
142
|
+
))}
|
|
143
|
+
</div>
|
|
144
|
+
</div>
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function resolvePreviewContext(
|
|
149
|
+
mode: LayoutFieldName,
|
|
150
|
+
option: string,
|
|
151
|
+
layoutContext?: LayoutContext,
|
|
152
|
+
): LayoutContext {
|
|
153
|
+
const fallback: LayoutContext = {
|
|
154
|
+
flexDirection: layoutContext?.flexDirection ?? 'row',
|
|
155
|
+
alignItems: layoutContext?.alignItems ?? 'center',
|
|
156
|
+
justifyContent: layoutContext?.justifyContent ?? 'flex-start',
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
switch (mode) {
|
|
160
|
+
case 'flexDirection':
|
|
161
|
+
return {
|
|
162
|
+
flexDirection: option as LayoutContext['flexDirection'],
|
|
163
|
+
alignItems: fallback.alignItems,
|
|
164
|
+
justifyContent: fallback.justifyContent,
|
|
165
|
+
};
|
|
166
|
+
case 'alignItems':
|
|
167
|
+
return {
|
|
168
|
+
flexDirection: fallback.flexDirection,
|
|
169
|
+
alignItems: option as LayoutContext['alignItems'],
|
|
170
|
+
justifyContent: fallback.justifyContent,
|
|
171
|
+
};
|
|
172
|
+
case 'justifyContent':
|
|
173
|
+
return {
|
|
174
|
+
flexDirection: fallback.flexDirection,
|
|
175
|
+
alignItems: fallback.alignItems,
|
|
176
|
+
justifyContent: option as LayoutContext['justifyContent'],
|
|
177
|
+
};
|
|
178
|
+
default:
|
|
179
|
+
return fallback;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function parseNumeric(value?: string | number | null) {
|
|
184
|
+
if (value === null || value === undefined) {
|
|
185
|
+
return undefined;
|
|
186
|
+
}
|
|
187
|
+
if (typeof value === 'number') {
|
|
188
|
+
return value;
|
|
189
|
+
}
|
|
190
|
+
const parsed = Number.parseFloat(value);
|
|
191
|
+
return Number.isNaN(parsed) ? undefined : parsed;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function formatLayoutLabel(option: string) {
|
|
195
|
+
return option
|
|
196
|
+
.replace(/[-_]/g, ' ')
|
|
197
|
+
.replace(/\s+/g, ' ')
|
|
198
|
+
.replace(/^\w|\s\w/g, (char) => char.toUpperCase());
|
|
199
|
+
}
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { NodeDefaultAttribute } from '../types/Node';
|
|
3
|
+
import { Field } from './Field';
|
|
4
|
+
import { FieldInfoTooltip } from './FieldInfoTooltip';
|
|
5
|
+
import {
|
|
6
|
+
AttributeMetaMap,
|
|
7
|
+
LayoutContext,
|
|
8
|
+
SchemaEntry,
|
|
9
|
+
isBooleanFieldType,
|
|
10
|
+
} from './types';
|
|
11
|
+
|
|
12
|
+
type SpecialCategorySectionProps = {
|
|
13
|
+
category: string;
|
|
14
|
+
entries: SchemaEntry[];
|
|
15
|
+
attributeMeta?: AttributeMetaMap;
|
|
16
|
+
attributes: NodeDefaultAttribute;
|
|
17
|
+
onAttributeChange: (name: string, value: unknown) => void;
|
|
18
|
+
componentType?: string;
|
|
19
|
+
projectColors?: string[];
|
|
20
|
+
layoutContext?: LayoutContext;
|
|
21
|
+
viewAttributes?: NodeDefaultAttribute;
|
|
22
|
+
meta?: {
|
|
23
|
+
label?: string;
|
|
24
|
+
description?: string;
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
type BoxFieldSlot = 'top' | 'bottom' | 'left' | 'right';
|
|
29
|
+
|
|
30
|
+
const FIELD_SLOT_MATCHERS: Record<BoxFieldSlot, RegExp> = {
|
|
31
|
+
top: /(^|[-_])(top)$/i,
|
|
32
|
+
bottom: /(^|[-_])(bottom)$/i,
|
|
33
|
+
left: /(^|[-_])(left)$/i,
|
|
34
|
+
right: /(^|[-_])(right)$/i,
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export function SpecialCategorySection({
|
|
38
|
+
category,
|
|
39
|
+
entries,
|
|
40
|
+
attributeMeta,
|
|
41
|
+
attributes,
|
|
42
|
+
onAttributeChange,
|
|
43
|
+
componentType,
|
|
44
|
+
projectColors,
|
|
45
|
+
layoutContext,
|
|
46
|
+
viewAttributes,
|
|
47
|
+
meta,
|
|
48
|
+
}: SpecialCategorySectionProps) {
|
|
49
|
+
const [showAdvanced, setShowAdvanced] = useState(false);
|
|
50
|
+
const normalizedTitle =
|
|
51
|
+
meta?.label && meta.label.trim().length > 0
|
|
52
|
+
? meta.label
|
|
53
|
+
: category && category.length > 0
|
|
54
|
+
? category.charAt(0).toUpperCase() + category.slice(1)
|
|
55
|
+
: 'Special';
|
|
56
|
+
const normalizedDescription = meta?.description;
|
|
57
|
+
const requiresAdvancedToggle =
|
|
58
|
+
category === 'padding' || category === 'margin' || category === 'offset';
|
|
59
|
+
|
|
60
|
+
const shouldUseBoxLayout = requiresAdvancedToggle;
|
|
61
|
+
|
|
62
|
+
const detectFieldSlot = (fieldName: string): BoxFieldSlot | undefined => {
|
|
63
|
+
if (!shouldUseBoxLayout) {
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
const normalized = fieldName
|
|
67
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1-$2')
|
|
68
|
+
.replace(/\s+/g, '-')
|
|
69
|
+
.toLowerCase();
|
|
70
|
+
return (Object.keys(FIELD_SLOT_MATCHERS) as BoxFieldSlot[]).find((slot) =>
|
|
71
|
+
FIELD_SLOT_MATCHERS[slot].test(normalized),
|
|
72
|
+
);
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const renderFields = () => {
|
|
76
|
+
if (!entries.length) {
|
|
77
|
+
return (
|
|
78
|
+
<p className="special-category-section__placeholder">
|
|
79
|
+
-- not defined --
|
|
80
|
+
</p>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
const fieldsClassNames = [
|
|
84
|
+
'special-category-section__fields',
|
|
85
|
+
shouldUseBoxLayout ? 'special-category-section__fields--box' : '',
|
|
86
|
+
]
|
|
87
|
+
.filter(Boolean)
|
|
88
|
+
.join(' ');
|
|
89
|
+
return (
|
|
90
|
+
<div className={fieldsClassNames}>
|
|
91
|
+
{entries.map(({ name, type }) => {
|
|
92
|
+
const label = attributeMeta?.[name]?.label ?? name;
|
|
93
|
+
const description = attributeMeta?.[name]?.description;
|
|
94
|
+
const preferredScale = attributeMeta?.[name]?.preferedScale;
|
|
95
|
+
const currentValue = (attributes as Record<string, unknown>)[name];
|
|
96
|
+
const isBoolean = isBooleanFieldType(type);
|
|
97
|
+
const fieldSlot = detectFieldSlot(name);
|
|
98
|
+
const wrapperClassNames = [
|
|
99
|
+
'attributes-editor__field-wrapper',
|
|
100
|
+
'special-category-section__field',
|
|
101
|
+
isBoolean ? 'attributes-editor__field-wrapper--boolean' : '',
|
|
102
|
+
]
|
|
103
|
+
.filter(Boolean)
|
|
104
|
+
.join(' ');
|
|
105
|
+
return (
|
|
106
|
+
<FieldInfoTooltip key={name} description={description}>
|
|
107
|
+
<div className={wrapperClassNames} data-field-slot={fieldSlot}>
|
|
108
|
+
{!isBoolean ? (
|
|
109
|
+
<p className="attributes-editor__field-label">{label}</p>
|
|
110
|
+
) : null}
|
|
111
|
+
<Field
|
|
112
|
+
name={name}
|
|
113
|
+
type={type}
|
|
114
|
+
value={currentValue}
|
|
115
|
+
onChange={(val) => onAttributeChange(name, val)}
|
|
116
|
+
componentType={componentType}
|
|
117
|
+
projectColors={projectColors}
|
|
118
|
+
layoutContext={layoutContext}
|
|
119
|
+
viewAttributes={viewAttributes}
|
|
120
|
+
label={isBoolean ? label : undefined}
|
|
121
|
+
preferredScale={preferredScale}
|
|
122
|
+
/>
|
|
123
|
+
</div>
|
|
124
|
+
</FieldInfoTooltip>
|
|
125
|
+
);
|
|
126
|
+
})}
|
|
127
|
+
</div>
|
|
128
|
+
);
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const renderContent = () => {
|
|
132
|
+
if (!entries.length) {
|
|
133
|
+
return (
|
|
134
|
+
<p className="special-category-section__placeholder">
|
|
135
|
+
-- not defined --
|
|
136
|
+
</p>
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (!requiresAdvancedToggle) {
|
|
141
|
+
return renderFields();
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Separate directional fields (paddingTop, etc.) from shorthand fields (padding, paddingHorizontal, etc.)
|
|
145
|
+
const boxEntries = entries
|
|
146
|
+
.filter((entry) => detectFieldSlot(entry.name))
|
|
147
|
+
.sort((a, b) => {
|
|
148
|
+
const slotOrder: BoxFieldSlot[] = ['top', 'left', 'right', 'bottom'];
|
|
149
|
+
const slotA = detectFieldSlot(a.name);
|
|
150
|
+
const slotB = detectFieldSlot(b.name);
|
|
151
|
+
const indexA = slotA ? slotOrder.indexOf(slotA) : 999;
|
|
152
|
+
const indexB = slotB ? slotOrder.indexOf(slotB) : 999;
|
|
153
|
+
return indexA - indexB;
|
|
154
|
+
});
|
|
155
|
+
const baseEntries = entries.filter((entry) => !detectFieldSlot(entry.name));
|
|
156
|
+
|
|
157
|
+
if (boxEntries.length === 0) {
|
|
158
|
+
return (
|
|
159
|
+
<p className="special-category-section__placeholder">
|
|
160
|
+
-- not defined --
|
|
161
|
+
</p>
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Always show directional fields in box layout
|
|
166
|
+
const boxFieldsJSX = (
|
|
167
|
+
<div className="special-category-section__fields special-category-section__fields--box">
|
|
168
|
+
{boxEntries.map(({ name, type }) => {
|
|
169
|
+
const label = attributeMeta?.[name]?.label ?? name;
|
|
170
|
+
const description = attributeMeta?.[name]?.description;
|
|
171
|
+
const preferredScale = attributeMeta?.[name]?.preferedScale;
|
|
172
|
+
const currentValue = (attributes as Record<string, unknown>)[name];
|
|
173
|
+
const isBoolean = isBooleanFieldType(type);
|
|
174
|
+
const fieldSlot = detectFieldSlot(name);
|
|
175
|
+
const wrapperClassNames = [
|
|
176
|
+
'attributes-editor__field-wrapper',
|
|
177
|
+
'special-category-section__field',
|
|
178
|
+
isBoolean ? 'attributes-editor__field-wrapper--boolean' : '',
|
|
179
|
+
]
|
|
180
|
+
.filter(Boolean)
|
|
181
|
+
.join(' ');
|
|
182
|
+
return (
|
|
183
|
+
<FieldInfoTooltip key={name} description={description}>
|
|
184
|
+
<div className={wrapperClassNames} data-field-slot={fieldSlot}>
|
|
185
|
+
{!isBoolean ? (
|
|
186
|
+
<p className="attributes-editor__field-label">{label}</p>
|
|
187
|
+
) : null}
|
|
188
|
+
<Field
|
|
189
|
+
name={name}
|
|
190
|
+
type={type}
|
|
191
|
+
value={currentValue}
|
|
192
|
+
onChange={(val) => onAttributeChange(name, val)}
|
|
193
|
+
componentType={componentType}
|
|
194
|
+
projectColors={projectColors}
|
|
195
|
+
layoutContext={layoutContext}
|
|
196
|
+
viewAttributes={viewAttributes}
|
|
197
|
+
label={isBoolean ? label : undefined}
|
|
198
|
+
preferredScale={preferredScale}
|
|
199
|
+
/>
|
|
200
|
+
</div>
|
|
201
|
+
</FieldInfoTooltip>
|
|
202
|
+
);
|
|
203
|
+
})}
|
|
204
|
+
</div>
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
// Show shorthand fields only when advanced is toggled
|
|
208
|
+
const baseFieldsJSX =
|
|
209
|
+
showAdvanced && baseEntries.length > 0 ? (
|
|
210
|
+
<div className="special-category-section__fields">
|
|
211
|
+
{baseEntries.map(({ name, type }) => {
|
|
212
|
+
const label = attributeMeta?.[name]?.label ?? name;
|
|
213
|
+
const description = attributeMeta?.[name]?.description;
|
|
214
|
+
const preferredScale = attributeMeta?.[name]?.preferedScale;
|
|
215
|
+
const currentValue = (attributes as Record<string, unknown>)[name];
|
|
216
|
+
const isBoolean = isBooleanFieldType(type);
|
|
217
|
+
const wrapperClassNames = [
|
|
218
|
+
'attributes-editor__field-wrapper',
|
|
219
|
+
'special-category-section__field',
|
|
220
|
+
isBoolean ? 'attributes-editor__field-wrapper--boolean' : '',
|
|
221
|
+
]
|
|
222
|
+
.filter(Boolean)
|
|
223
|
+
.join(' ');
|
|
224
|
+
return (
|
|
225
|
+
<FieldInfoTooltip key={name} description={description}>
|
|
226
|
+
<div className={wrapperClassNames}>
|
|
227
|
+
{!isBoolean ? (
|
|
228
|
+
<p className="attributes-editor__field-label">{label}</p>
|
|
229
|
+
) : null}
|
|
230
|
+
<Field
|
|
231
|
+
name={name}
|
|
232
|
+
type={type}
|
|
233
|
+
value={currentValue}
|
|
234
|
+
onChange={(val) => onAttributeChange(name, val)}
|
|
235
|
+
componentType={componentType}
|
|
236
|
+
projectColors={projectColors}
|
|
237
|
+
layoutContext={layoutContext}
|
|
238
|
+
viewAttributes={viewAttributes}
|
|
239
|
+
label={isBoolean ? label : undefined}
|
|
240
|
+
preferredScale={preferredScale}
|
|
241
|
+
/>
|
|
242
|
+
</div>
|
|
243
|
+
</FieldInfoTooltip>
|
|
244
|
+
);
|
|
245
|
+
})}
|
|
246
|
+
</div>
|
|
247
|
+
) : null;
|
|
248
|
+
|
|
249
|
+
return (
|
|
250
|
+
<>
|
|
251
|
+
{boxFieldsJSX}
|
|
252
|
+
{baseFieldsJSX}
|
|
253
|
+
</>
|
|
254
|
+
);
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
// Check if we have shorthand fields to show the toggle
|
|
258
|
+
const baseEntries = entries.filter((entry) => !detectFieldSlot(entry.name));
|
|
259
|
+
const shouldShowToggle = requiresAdvancedToggle && baseEntries.length > 0;
|
|
260
|
+
|
|
261
|
+
return (
|
|
262
|
+
<section className="special-category-section">
|
|
263
|
+
<div className="special-category-section__header">
|
|
264
|
+
<p className="special-category-section__title">{normalizedTitle}</p>
|
|
265
|
+
{shouldShowToggle ? (
|
|
266
|
+
<button
|
|
267
|
+
type="button"
|
|
268
|
+
onClick={() => setShowAdvanced((prev) => !prev)}
|
|
269
|
+
className="special-category-section__toggle"
|
|
270
|
+
data-active={showAdvanced}
|
|
271
|
+
>
|
|
272
|
+
{showAdvanced ? 'Hide advanced' : 'Show advanced'}
|
|
273
|
+
</button>
|
|
274
|
+
) : null}
|
|
275
|
+
</div>
|
|
276
|
+
{normalizedDescription ? (
|
|
277
|
+
<p className="special-category-section__description">
|
|
278
|
+
{normalizedDescription}
|
|
279
|
+
</p>
|
|
280
|
+
) : null}
|
|
281
|
+
{renderContent()}
|
|
282
|
+
</section>
|
|
283
|
+
);
|
|
284
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AlignItemsOptionType,
|
|
3
|
+
FlexDirectionOptionType,
|
|
4
|
+
JustifyContentOptionType,
|
|
5
|
+
} from '../build-components/View/ViewProps.generated';
|
|
6
|
+
import { getAttributeMeta } from '../utils/patterns';
|
|
7
|
+
|
|
8
|
+
export type SchemaEntry = {
|
|
9
|
+
name: string;
|
|
10
|
+
type: string | string[];
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export type AttributeMetaMap = ReturnType<typeof getAttributeMeta>;
|
|
14
|
+
|
|
15
|
+
export type LayoutFieldName = 'flexDirection' | 'alignItems' | 'justifyContent';
|
|
16
|
+
|
|
17
|
+
export type LayoutContext = {
|
|
18
|
+
flexDirection?: FlexDirectionOptionType;
|
|
19
|
+
alignItems?: AlignItemsOptionType;
|
|
20
|
+
justifyContent?: JustifyContentOptionType;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const BOOLEAN_ALIASES = new Set(['bool', 'boolean']);
|
|
24
|
+
|
|
25
|
+
export function isBooleanFieldType(type: string | string[]): boolean {
|
|
26
|
+
if (Array.isArray(type)) return false;
|
|
27
|
+
if (typeof type !== 'string') return false;
|
|
28
|
+
const normalized = type.trim().toLowerCase();
|
|
29
|
+
return BOOLEAN_ALIASES.has(normalized);
|
|
30
|
+
}
|
|
@@ -1,12 +1,20 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { useMemo } from 'react';
|
|
2
2
|
import type { ButtonComponentProps } from './ButtonProps.generated';
|
|
3
3
|
import useNode from '../useNode';
|
|
4
4
|
import { useLogRender } from '../../utils/useLogRender';
|
|
5
|
+
import { extractViewStyle } from '../../utils/extractViewStyle';
|
|
5
6
|
|
|
6
7
|
function Button({ node }: ButtonComponentProps) {
|
|
7
8
|
useLogRender('Button');
|
|
8
9
|
node = useNode(node);
|
|
9
|
-
|
|
10
|
+
const attributeKey = (node as any)?.sourceType ?? node.type ?? 'button';
|
|
11
|
+
const style = useMemo(() => extractViewStyle(node), [node]);
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<div attribute-key={attributeKey} style={style}>
|
|
15
|
+
{String(node?.type ?? 'button')}
|
|
16
|
+
</div>
|
|
17
|
+
);
|
|
10
18
|
}
|
|
11
19
|
|
|
12
20
|
export default React.memo(Button);
|
|
@@ -2,6 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
import type { NodeData } from '../../types/Node';
|
|
4
4
|
|
|
5
|
+
export type FlexDirectionOptionType = 'row' | 'column';
|
|
6
|
+
export type AlignItemsOptionType =
|
|
7
|
+
| 'flex-start'
|
|
8
|
+
| 'center'
|
|
9
|
+
| 'flex-end'
|
|
10
|
+
| 'stretch'
|
|
11
|
+
| 'baseline';
|
|
12
|
+
export type JustifyContentOptionType =
|
|
13
|
+
| 'flex-start'
|
|
14
|
+
| 'center'
|
|
15
|
+
| 'flex-end'
|
|
16
|
+
| 'space-between'
|
|
17
|
+
| 'space-around'
|
|
18
|
+
| 'space-evenly';
|
|
5
19
|
export type FontWeightOptionType =
|
|
6
20
|
| 'normal'
|
|
7
21
|
| 'bold'
|
|
@@ -18,8 +32,30 @@ export type FontWeightOptionType =
|
|
|
18
32
|
export interface ButtonPropsGenerated {
|
|
19
33
|
child: string;
|
|
20
34
|
attributes: {
|
|
35
|
+
scrollable?: boolean;
|
|
36
|
+
flexDirection?: FlexDirectionOptionType;
|
|
37
|
+
alignItems?: AlignItemsOptionType;
|
|
38
|
+
justifyContent?: JustifyContentOptionType;
|
|
39
|
+
gap?: string;
|
|
40
|
+
padding?: string;
|
|
41
|
+
paddingHorizontal?: string;
|
|
42
|
+
paddingVertical?: string;
|
|
43
|
+
paddingTop?: string;
|
|
44
|
+
paddingBottom?: string;
|
|
45
|
+
paddingLeft?: string;
|
|
46
|
+
paddingRight?: string;
|
|
47
|
+
margin?: string;
|
|
48
|
+
marginVertical?: string;
|
|
49
|
+
marginTop?: string;
|
|
50
|
+
marginBottom?: string;
|
|
51
|
+
marginLeft?: string;
|
|
52
|
+
marginRight?: string;
|
|
53
|
+
backgroundColor?: string;
|
|
54
|
+
borderRadius?: string;
|
|
55
|
+
width?: string;
|
|
56
|
+
height?: string;
|
|
21
57
|
color?: string;
|
|
22
|
-
fontSize?:
|
|
58
|
+
fontSize?: string;
|
|
23
59
|
fontWeight?: FontWeightOptionType;
|
|
24
60
|
};
|
|
25
61
|
}
|
|
@@ -4,9 +4,10 @@
|
|
|
4
4
|
"pattern": {
|
|
5
5
|
"type": "button",
|
|
6
6
|
"children": "string",
|
|
7
|
+
"extends": "View",
|
|
7
8
|
"attributes": {
|
|
8
|
-
"color": "
|
|
9
|
-
"fontSize": "
|
|
9
|
+
"color": "color",
|
|
10
|
+
"fontSize": "size",
|
|
10
11
|
"fontWeight": [
|
|
11
12
|
"normal",
|
|
12
13
|
"bold",
|
|
@@ -21,5 +22,33 @@
|
|
|
21
22
|
"900"
|
|
22
23
|
]
|
|
23
24
|
}
|
|
25
|
+
},
|
|
26
|
+
"meta": {
|
|
27
|
+
"desiredParent": ["all"],
|
|
28
|
+
"label": "Button",
|
|
29
|
+
"description": "Simple action button.",
|
|
30
|
+
"attributes": {
|
|
31
|
+
"color": {
|
|
32
|
+
"label": "Color",
|
|
33
|
+
"description": "Text color of the button.",
|
|
34
|
+
"category": "style",
|
|
35
|
+
"specialCategory": null,
|
|
36
|
+
"sort": 1
|
|
37
|
+
},
|
|
38
|
+
"fontSize": {
|
|
39
|
+
"label": "Font Size",
|
|
40
|
+
"description": "Text size of the button.",
|
|
41
|
+
"category": "style",
|
|
42
|
+
"specialCategory": null,
|
|
43
|
+
"sort": 2
|
|
44
|
+
},
|
|
45
|
+
"fontWeight": {
|
|
46
|
+
"label": "Font Weight",
|
|
47
|
+
"description": "Text weight of the button.",
|
|
48
|
+
"category": "style",
|
|
49
|
+
"specialCategory": null,
|
|
50
|
+
"sort": 3
|
|
51
|
+
}
|
|
52
|
+
}
|
|
24
53
|
}
|
|
25
54
|
}
|
|
@@ -1,13 +1,16 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { useMemo } from 'react';
|
|
2
2
|
import type { CarouselComponentProps } from './CarouselProps.generated';
|
|
3
3
|
import RenderNode from '../RenderNode.generated';
|
|
4
4
|
import { isCarouselItem } from '../../utils/isCarousel';
|
|
5
5
|
import useNode from '../useNode';
|
|
6
6
|
import { useLogRender } from '../../utils/useLogRender';
|
|
7
|
+
import { extractViewStyle } from '../../utils/extractViewStyle';
|
|
7
8
|
|
|
8
9
|
function Carousel({ node }: CarouselComponentProps) {
|
|
9
10
|
useLogRender('Carousel');
|
|
10
11
|
node = useNode(node);
|
|
12
|
+
const attributeKey = (node as any)?.sourceType ?? node.type ?? 'carousel';
|
|
13
|
+
const style = useMemo(() => extractViewStyle(node), [node]);
|
|
11
14
|
// Ensure children are carouselItems
|
|
12
15
|
const renderChildren = () => {
|
|
13
16
|
if (Array.isArray(node.children)) {
|
|
@@ -20,7 +23,17 @@ function Carousel({ node }: CarouselComponentProps) {
|
|
|
20
23
|
) : null;
|
|
21
24
|
};
|
|
22
25
|
|
|
23
|
-
|
|
26
|
+
//NOTE: I don't know how to force declare attribute-key. It must be added/checked manually.
|
|
27
|
+
//TODO: add to md file for agents
|
|
28
|
+
return (
|
|
29
|
+
<div
|
|
30
|
+
attribute-key={attributeKey}
|
|
31
|
+
className="embla__container"
|
|
32
|
+
style={style}
|
|
33
|
+
>
|
|
34
|
+
{renderChildren()}
|
|
35
|
+
</div>
|
|
36
|
+
);
|
|
24
37
|
}
|
|
25
38
|
|
|
26
39
|
export default React.memo(Carousel);
|