@developer_tribe/react-builder 1.2.7 → 1.2.9
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 +2 -11
- package/dist/attribute-analyser/style/native/useExtractImageStyle.d.ts +10 -0
- package/dist/attribute-analyser/style/native/useExtractTextStyle.d.ts +9 -0
- package/dist/attribute-analyser/style/native/useExtractViewStyle.d.ts +8 -0
- package/dist/attribute-analyser/style/web/useExtractImageStyle.d.ts +4 -0
- package/dist/attribute-analyser/style/web/useExtractTextStyle.d.ts +4 -0
- package/dist/attribute-analyser/style/web/useExtractViewStyle.d.ts +4 -0
- package/dist/attributes-editor/AttributesEditorFields.d.ts +18 -0
- package/dist/attributes-editor/AttributesEditorView.d.ts +4 -0
- package/dist/attributes-editor/attributesEditorModelTypes.d.ts +67 -0
- package/dist/attributes-editor/attributesEditorUtils.d.ts +19 -0
- package/dist/attributes-editor/useAttributesEditorModel.d.ts +2 -0
- package/dist/build-components/BIcon/BIconProps.generated.d.ts +41 -38
- package/dist/build-components/BackgroundImage/BackgroundImageProps.generated.d.ts +37 -34
- package/dist/build-components/Button/ButtonProps.generated.d.ts +39 -36
- package/dist/build-components/Carousel/CarouselProps.generated.d.ts +37 -34
- package/dist/build-components/CarouselButtons/CarouselButtonsProps.generated.d.ts +37 -34
- package/dist/build-components/CarouselDots/CarouselDotsProps.generated.d.ts +37 -34
- package/dist/build-components/CarouselItem/CarouselItemProps.generated.d.ts +36 -33
- package/dist/build-components/CarouselProvider/CarouselProviderProps.generated.d.ts +36 -33
- package/dist/build-components/Image/ImageProps.generated.d.ts +38 -33
- package/dist/build-components/Main/MainProps.generated.d.ts +36 -33
- package/dist/build-components/Onboard/OnboardProps.generated.d.ts +36 -33
- package/dist/build-components/OnboardButton/OnboardButtonProps.generated.d.ts +38 -34
- package/dist/build-components/OnboardButtons/OnboardButtonsProps.generated.d.ts +39 -36
- package/dist/build-components/OnboardDot/OnboardDotProps.generated.d.ts +43 -34
- package/dist/build-components/OnboardFooter/OnboardFooterProps.generated.d.ts +41 -38
- package/dist/build-components/OnboardImage/OnboardImageProps.generated.d.ts +36 -31
- package/dist/build-components/OnboardItem/OnboardItemProps.generated.d.ts +39 -33
- package/dist/build-components/OnboardProvider/OnboardProviderProps.generated.d.ts +38 -34
- package/dist/build-components/OnboardSubtitle/OnboardSubtitleProps.generated.d.ts +41 -38
- package/dist/build-components/OnboardTitle/OnboardTitleProps.generated.d.ts +41 -38
- package/dist/build-components/PaywallBackground/PaywallBackgroundProps.generated.d.ts +36 -33
- package/dist/build-components/PaywallCloseButton/PaywallCloseButtonProps.generated.d.ts +41 -38
- package/dist/build-components/PaywallOptions/PaywallOptionsProps.generated.d.ts +36 -33
- package/dist/build-components/PaywallProvider/PaywallProviderProps.generated.d.ts +36 -33
- package/dist/build-components/PaywallSubscribeButton/PaywallSubscribeButtonProps.generated.d.ts +39 -36
- package/dist/build-components/RadioButton/RadioButtonProps.generated.d.ts +36 -33
- package/dist/build-components/Text/TextProps.generated.d.ts +41 -38
- package/dist/build-components/View/ViewProps.generated.d.ts +36 -33
- package/dist/build-components/patterns.generated.d.ts +2673 -5787
- package/dist/components/BuilderProvider.d.ts +6 -0
- package/dist/index.cjs.js +5 -5
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +5 -26
- package/dist/index.esm.js +5 -5
- package/dist/index.esm.js.map +1 -1
- package/dist/index.native.cjs.js +6 -4
- package/dist/index.native.cjs.js.map +1 -1
- package/dist/index.native.d.ts +6 -3
- package/dist/index.native.esm.js +6 -4
- package/dist/index.native.esm.js.map +1 -1
- package/dist/migrations/migratePipe.d.ts +1 -1
- package/dist/migrations/migrations/1.1.2_extract_component_attributes_from_style.d.ts +2 -0
- package/dist/mockOS/components/PermissionModal.d.ts +1 -2
- package/dist/styles.css +1 -1
- package/dist/types/PreviewConfig.d.ts +1 -5
- package/dist/utils/extractImageStyle.d.ts +3 -0
- package/dist/utils/extractTextStyle/extractTextStyleNative.d.ts +17 -0
- package/dist/utils/extractTextStyle.d.ts +2 -0
- package/dist/utils/extractViewStyle/extractViewStyleNative.d.ts +12 -0
- package/dist/utils/extractViewStyle.d.ts +2 -0
- package/dist/utils/getMeta.d.ts +5 -0
- package/dist/utils/patterns.d.ts +14 -1
- package/package.json +2 -1
- package/scripts/prebuild/prebuild.js +14 -0
- package/scripts/prebuild/utils/createGeneratedProps.js +51 -3
- package/scripts/prebuild/utils/index.js +1 -0
- package/scripts/prebuild/utils/updateMetaJson.js +66 -0
- package/scripts/prebuild/utils/validateAllComponentsOrThrow.js +37 -3
- package/scripts/prebuild/utils/validatePatternJson.js +27 -2
- package/scripts/public/scripts/build/index.js +20 -3
- package/scripts/public/scripts/build/info.json +6 -0
- package/scripts/public/scripts/build/utils/createComponentsIndex.js +9 -3
- package/scripts/public/scripts/build/utils/createRenderNodeGenerated.js +66 -8
- package/src/AttributesEditor.tsx +8 -944
- package/src/assets/meta.json +4 -0
- package/src/assets/samples/carousel-sample.json +1 -1
- package/src/assets/samples/getSamples.ts +2 -0
- package/src/assets/samples/paywall-1.json +11 -7
- package/src/assets/samples/simple-1.json +3 -3
- package/src/assets/samples/simple-2.json +3 -3
- package/src/assets/samples/unmigrated-builder-1.1.1.json +87 -0
- package/src/assets/samples/unmigrated-builder1.json +1 -1
- package/src/assets/samples/unvalidated-builder1.json +3 -3
- package/src/assets/samples/unvalidated-crash1.json +1 -1
- package/src/assets/samples/unvalidated-crashcomponent1.json +1 -1
- package/src/assets/samples/vpn-onboard-1.json +1 -1
- package/src/assets/samples/vpn-onboard-2.json +1 -1
- package/src/assets/samples/vpn-onboard-3.json +1 -1
- package/src/assets/samples/vpn-onboard-4.json +1 -1
- package/src/assets/samples/vpn-onboard-5.json +1 -1
- package/src/assets/samples/vpn-onboard-6.json +1 -1
- package/src/attribute-analyser/style/native/useExtractImageStyle.ts +46 -0
- package/src/attribute-analyser/style/native/useExtractTextStyle.ts +50 -0
- package/src/attribute-analyser/style/native/useExtractViewStyle.ts +32 -0
- package/src/attribute-analyser/style/web/useExtractImageStyle.ts +20 -0
- package/src/{hooks → attribute-analyser/style/web}/useExtractTextStyle.ts +7 -6
- package/src/{hooks → attribute-analyser/style/web}/useExtractViewStyle.ts +7 -6
- package/src/attributes-editor/AttributesEditorFields.tsx +248 -0
- package/src/attributes-editor/AttributesEditorView.tsx +360 -0
- package/src/attributes-editor/LayoutPreviewPicker.tsx +4 -3
- package/src/attributes-editor/attributesEditorModelTypes.ts +86 -0
- package/src/attributes-editor/attributesEditorUtils.ts +102 -0
- package/src/attributes-editor/useAttributesEditorModel.ts +477 -0
- package/src/build-components/BIcon/BIcon.tsx +4 -3
- package/src/build-components/BIcon/BIconProps.generated.ts +42 -38
- package/src/build-components/BIcon/pattern.json +5 -6
- package/src/build-components/BackgroundImage/BackgroundImage.tsx +7 -4
- package/src/build-components/BackgroundImage/BackgroundImageProps.generated.ts +38 -34
- package/src/build-components/BackgroundImage/pattern.json +9 -17
- package/src/build-components/Button/Button.tsx +7 -6
- package/src/build-components/Button/ButtonProps.generated.ts +40 -36
- package/src/build-components/Button/pattern.json +17 -15
- package/src/build-components/Carousel/Carousel.tsx +1 -1
- package/src/build-components/Carousel/CarouselProps.generated.ts +38 -34
- package/src/build-components/CarouselButtons/CarouselButtons.tsx +4 -6
- package/src/build-components/CarouselButtons/CarouselButtonsProps.generated.ts +41 -37
- package/src/build-components/CarouselButtons/pattern.json +2 -1
- package/src/build-components/CarouselDots/CarouselDots.tsx +2 -2
- package/src/build-components/CarouselDots/CarouselDotsProps.generated.ts +44 -40
- package/src/build-components/CarouselItem/CarouselItem.tsx +1 -1
- package/src/build-components/CarouselItem/CarouselItemProps.generated.ts +37 -33
- package/src/build-components/CarouselProvider/CarouselProvider.tsx +1 -1
- package/src/build-components/CarouselProvider/CarouselProviderProps.generated.ts +37 -33
- package/src/build-components/Image/Image.tsx +4 -3
- package/src/build-components/Image/ImageProps.generated.ts +39 -33
- package/src/build-components/Image/pattern.json +5 -11
- package/src/build-components/Main/Main.tsx +1 -1
- package/src/build-components/Main/MainProps.generated.ts +37 -33
- package/src/build-components/Main/pattern.json +2 -1
- package/src/build-components/Onboard/OnboardProps.generated.ts +37 -33
- package/src/build-components/OnboardButton/OnboardButton.tsx +8 -6
- package/src/build-components/OnboardButton/OnboardButtonProps.generated.ts +44 -39
- package/src/build-components/OnboardButton/pattern.json +9 -7
- package/src/build-components/OnboardButtons/OnboardButtons.tsx +31 -31
- package/src/build-components/OnboardButtons/OnboardButtonsProps.generated.ts +43 -39
- package/src/build-components/OnboardButtons/pattern.json +9 -7
- package/src/build-components/OnboardDot/OnboardDot.tsx +7 -5
- package/src/build-components/OnboardDot/OnboardDotProps.generated.ts +55 -34
- package/src/build-components/OnboardFooter/OnboardFooter.tsx +19 -23
- package/src/build-components/OnboardFooter/OnboardFooterProps.generated.ts +42 -38
- package/src/build-components/OnboardFooter/pattern.json +16 -14
- package/src/build-components/OnboardImage/OnboardImage.tsx +8 -7
- package/src/build-components/OnboardImage/OnboardImageProps.generated.ts +37 -31
- package/src/build-components/OnboardImage/pattern.json +2 -1
- package/src/build-components/OnboardItem/OnboardItem.tsx +1 -1
- package/src/build-components/OnboardItem/OnboardItemProps.generated.ts +40 -33
- package/src/build-components/OnboardItem/pattern.json +2 -1
- package/src/build-components/OnboardProvider/OnboardProvider.tsx +1 -1
- package/src/build-components/OnboardProvider/OnboardProviderProps.generated.ts +39 -34
- package/src/build-components/OnboardProvider/pattern.json +2 -1
- package/src/build-components/OnboardSubtitle/OnboardSubtitleProps.generated.ts +42 -38
- package/src/build-components/OnboardTitle/OnboardTitleProps.generated.ts +42 -38
- package/src/build-components/PaywallBackground/PaywallBackground.tsx +1 -1
- package/src/build-components/PaywallBackground/PaywallBackgroundProps.generated.ts +37 -33
- package/src/build-components/PaywallCloseButton/PaywallCloseButton.tsx +6 -5
- package/src/build-components/PaywallCloseButton/PaywallCloseButtonProps.generated.ts +42 -38
- package/src/build-components/PaywallOptions/PaywallOptionButton.tsx +1 -1
- package/src/build-components/PaywallOptions/PaywallOptionsProps.generated.ts +37 -33
- package/src/build-components/PaywallProvider/PaywallProvider.tsx +1 -1
- package/src/build-components/PaywallProvider/PaywallProviderProps.generated.ts +37 -33
- package/src/build-components/PaywallSubscribeButton/PaywallSubscribeButton.tsx +1 -1
- package/src/build-components/PaywallSubscribeButton/PaywallSubscribeButtonProps.generated.ts +40 -36
- package/src/build-components/RadioButton/RadioButton.tsx +5 -4
- package/src/build-components/RadioButton/RadioButtonProps.generated.ts +37 -33
- package/src/build-components/RadioButton/pattern.json +9 -7
- package/src/build-components/Text/Text.tsx +6 -8
- package/src/build-components/Text/TextProps.generated.ts +42 -38
- package/src/build-components/Text/pattern.json +15 -11
- package/src/build-components/View/View.tsx +1 -1
- package/src/build-components/View/ViewProps.generated.ts +37 -33
- package/src/build-components/View/pattern.json +71 -66
- package/src/build-components/patterns.generated.ts +3022 -5971
- package/src/components/AttributesEditorPanel.tsx +2 -2
- package/src/components/BuilderProvider.tsx +15 -1
- package/src/index.native.ts +7 -4
- package/src/index.ts +6 -77
- package/src/migrations/migratePipe.ts +7 -3
- package/src/migrations/migrations/1.1.2_extract_component_attributes_from_style.ts +211 -0
- package/src/mockOS/components/MockOSRouter.tsx +3 -1
- package/src/mockOS/components/PermissionModal.tsx +20 -160
- package/src/mockOS/components/SubscriptionModal.tsx +41 -278
- package/src/pages/ProjectPage.tsx +12 -6
- package/src/styles/components/_attributes-editor.scss +122 -0
- package/src/styles/components/_mockos-router.scss +388 -0
- package/src/styles/components/_onboard.scss +23 -0
- package/src/styles/index.scss +1 -0
- package/src/types/PreviewConfig.ts +1 -5
- package/src/utils/analyseNodeByPatterns.ts +39 -4
- package/src/utils/extractImageStyle.ts +34 -5
- package/src/utils/extractTextStyle/extractTextStyle.ts +7 -6
- package/src/utils/extractTextStyle/extractTextStyleNative.ts +106 -0
- package/src/utils/extractTextStyle.ts +2 -0
- package/src/utils/extractViewStyle/extractViewStyle.ts +2 -4
- package/src/utils/extractViewStyle/extractViewStyleNative.ts +111 -0
- package/src/utils/extractViewStyle.ts +2 -0
- package/src/utils/getMeta.ts +15 -0
- package/src/utils/patterns.ts +100 -3
- package/dist/hooks/useExtractImageStyle.d.ts +0 -3
- package/dist/hooks/useExtractTextStyle.d.ts +0 -3
- package/dist/hooks/useExtractViewStyle.d.ts +0 -3
- package/src/hooks/useExtractImageStyle.ts +0 -19
- package/src/migrations/migrations/1.1.0_normalize_style_attributes.ts +0 -80
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import React, { useMemo, useState } from 'react';
|
|
2
|
+
import type { Fonts } from '../types/Fonts';
|
|
3
|
+
import { Icon } from '../components/Icon.generated';
|
|
4
|
+
import { IconPickerModal } from '../modals/IconPickerModal';
|
|
5
|
+
import Modal from '../modals/Modal';
|
|
6
|
+
import { loadFontFamily } from '../utils/loadFontFamily';
|
|
7
|
+
import { fontsDebug } from '../utils/fontsDebug';
|
|
8
|
+
import type { IconsType } from '../types/Icons';
|
|
9
|
+
|
|
10
|
+
type IconTypePickerFieldProps = {
|
|
11
|
+
name: string;
|
|
12
|
+
value: unknown;
|
|
13
|
+
onChange: (next: unknown) => void;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export function IconTypePickerField({
|
|
17
|
+
name,
|
|
18
|
+
value,
|
|
19
|
+
onChange,
|
|
20
|
+
}: IconTypePickerFieldProps) {
|
|
21
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
22
|
+
const normalized =
|
|
23
|
+
typeof value === 'string' ? (value as IconsType) : undefined;
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<>
|
|
27
|
+
<button
|
|
28
|
+
type="button"
|
|
29
|
+
onClick={() => setIsOpen(true)}
|
|
30
|
+
className="attributes-editor__picker-button"
|
|
31
|
+
>
|
|
32
|
+
<span className="attributes-editor__picker-button-left">
|
|
33
|
+
{normalized ? <Icon iconType={normalized} size={18} /> : null}
|
|
34
|
+
<span className="attributes-editor__picker-button-label">
|
|
35
|
+
{normalized ?? 'Select icon'}
|
|
36
|
+
</span>
|
|
37
|
+
</span>
|
|
38
|
+
<span className="attributes-editor__picker-button-open">Open</span>
|
|
39
|
+
</button>
|
|
40
|
+
|
|
41
|
+
{isOpen ? (
|
|
42
|
+
<IconPickerModal
|
|
43
|
+
value={normalized}
|
|
44
|
+
onSelect={(iconName) => {
|
|
45
|
+
onChange(iconName);
|
|
46
|
+
setIsOpen(false);
|
|
47
|
+
}}
|
|
48
|
+
onClose={() => setIsOpen(false)}
|
|
49
|
+
onClear={() => {
|
|
50
|
+
onChange(undefined);
|
|
51
|
+
setIsOpen(false);
|
|
52
|
+
}}
|
|
53
|
+
/>
|
|
54
|
+
) : null}
|
|
55
|
+
</>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
type FontFamilyPickerFieldProps = {
|
|
60
|
+
name: string;
|
|
61
|
+
value: unknown;
|
|
62
|
+
onChange: (next: unknown) => void;
|
|
63
|
+
fonts: Fonts;
|
|
64
|
+
loadedFonts: string[] | undefined;
|
|
65
|
+
markFontLoaded: (fontFamily: string) => void;
|
|
66
|
+
addError: (message: string) => void;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export function FontFamilyPickerField({
|
|
70
|
+
name,
|
|
71
|
+
value,
|
|
72
|
+
onChange,
|
|
73
|
+
fonts,
|
|
74
|
+
loadedFonts,
|
|
75
|
+
markFontLoaded,
|
|
76
|
+
addError,
|
|
77
|
+
}: FontFamilyPickerFieldProps) {
|
|
78
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
79
|
+
const [isFontLoading, setIsFontLoading] = useState(false);
|
|
80
|
+
const [fontLoadError, setFontLoadError] = useState<string | null>(null);
|
|
81
|
+
|
|
82
|
+
const normalized =
|
|
83
|
+
typeof value === 'string' && value.trim().length > 0
|
|
84
|
+
? value.trim()
|
|
85
|
+
: undefined;
|
|
86
|
+
|
|
87
|
+
const fontsList = Array.isArray(fonts) ? fonts : [];
|
|
88
|
+
const loaded = Array.isArray(loadedFonts) ? loadedFonts : [];
|
|
89
|
+
|
|
90
|
+
const fontCountLabel = useMemo(
|
|
91
|
+
() => `${fontsList.length} fonts`,
|
|
92
|
+
[fontsList.length],
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
return (
|
|
96
|
+
<>
|
|
97
|
+
<button
|
|
98
|
+
type="button"
|
|
99
|
+
onClick={() => {
|
|
100
|
+
setFontLoadError(null);
|
|
101
|
+
setIsOpen(true);
|
|
102
|
+
}}
|
|
103
|
+
className="attributes-editor__picker-button"
|
|
104
|
+
>
|
|
105
|
+
<span className="attributes-editor__picker-button-label attributes-editor__picker-button-label--fill">
|
|
106
|
+
{normalized ?? 'Select font'}
|
|
107
|
+
</span>
|
|
108
|
+
<span className="attributes-editor__picker-button-open">Open</span>
|
|
109
|
+
</button>
|
|
110
|
+
|
|
111
|
+
{isOpen ? (
|
|
112
|
+
<Modal
|
|
113
|
+
ariaLabelledBy="font-family-picker-title"
|
|
114
|
+
onClose={() => {
|
|
115
|
+
if (isFontLoading) return;
|
|
116
|
+
setIsOpen(false);
|
|
117
|
+
}}
|
|
118
|
+
closeOnOverlayClick={!isFontLoading}
|
|
119
|
+
closeOnEsc={!isFontLoading}
|
|
120
|
+
>
|
|
121
|
+
<div className="attributes-editor__font-modal">
|
|
122
|
+
<div className="attributes-editor__font-modal-header">
|
|
123
|
+
<p
|
|
124
|
+
id="font-family-picker-title"
|
|
125
|
+
className="attributes-editor__font-modal-title"
|
|
126
|
+
>
|
|
127
|
+
Select Font
|
|
128
|
+
</p>
|
|
129
|
+
<div className="attributes-editor__font-modal-spacer" />
|
|
130
|
+
<button
|
|
131
|
+
type="button"
|
|
132
|
+
className="editor-button"
|
|
133
|
+
onClick={() => {
|
|
134
|
+
onChange(undefined);
|
|
135
|
+
setFontLoadError(null);
|
|
136
|
+
setIsOpen(false);
|
|
137
|
+
}}
|
|
138
|
+
disabled={isFontLoading}
|
|
139
|
+
>
|
|
140
|
+
Clear
|
|
141
|
+
</button>
|
|
142
|
+
<button
|
|
143
|
+
type="button"
|
|
144
|
+
className="editor-button"
|
|
145
|
+
onClick={() => setIsOpen(false)}
|
|
146
|
+
disabled={isFontLoading}
|
|
147
|
+
>
|
|
148
|
+
Close
|
|
149
|
+
</button>
|
|
150
|
+
</div>
|
|
151
|
+
|
|
152
|
+
{isFontLoading ? (
|
|
153
|
+
<div className="attributes-editor__font-modal-note">
|
|
154
|
+
Loading font…
|
|
155
|
+
</div>
|
|
156
|
+
) : null}
|
|
157
|
+
{fontLoadError ? (
|
|
158
|
+
<div className="attributes-editor__font-modal-error">
|
|
159
|
+
{fontLoadError}
|
|
160
|
+
</div>
|
|
161
|
+
) : null}
|
|
162
|
+
|
|
163
|
+
<div className="attributes-editor__font-modal-note">
|
|
164
|
+
{fontCountLabel}
|
|
165
|
+
</div>
|
|
166
|
+
|
|
167
|
+
<div className="attributes-editor__font-grid">
|
|
168
|
+
{fontsList.map((font) => {
|
|
169
|
+
const fontName = font?.name;
|
|
170
|
+
if (typeof fontName !== 'string' || !fontName.trim()) {
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
const familyName = fontName.trim();
|
|
174
|
+
const isActive = normalized === familyName;
|
|
175
|
+
const isLoaded = loaded.includes(familyName);
|
|
176
|
+
const optionClassName = [
|
|
177
|
+
'attributes-editor__font-option',
|
|
178
|
+
isActive ? 'attributes-editor__font-option--active' : '',
|
|
179
|
+
]
|
|
180
|
+
.filter(Boolean)
|
|
181
|
+
.join(' ');
|
|
182
|
+
|
|
183
|
+
return (
|
|
184
|
+
<button
|
|
185
|
+
key={familyName}
|
|
186
|
+
type="button"
|
|
187
|
+
disabled={isFontLoading}
|
|
188
|
+
onClick={async () => {
|
|
189
|
+
fontsDebug.info('AttributesEditor: select fontFamily', {
|
|
190
|
+
field: name,
|
|
191
|
+
familyName,
|
|
192
|
+
wasLoaded: isLoaded,
|
|
193
|
+
});
|
|
194
|
+
setFontLoadError(null);
|
|
195
|
+
onChange(familyName);
|
|
196
|
+
if (isLoaded) return;
|
|
197
|
+
|
|
198
|
+
setIsFontLoading(true);
|
|
199
|
+
try {
|
|
200
|
+
fontsDebug.info(
|
|
201
|
+
'AttributesEditor: loadFontFamily start',
|
|
202
|
+
{ familyName },
|
|
203
|
+
);
|
|
204
|
+
await loadFontFamily(fontsList, familyName, {
|
|
205
|
+
forceFetch: true,
|
|
206
|
+
});
|
|
207
|
+
markFontLoaded(familyName);
|
|
208
|
+
fontsDebug.info(
|
|
209
|
+
'AttributesEditor: loadFontFamily success',
|
|
210
|
+
{ familyName },
|
|
211
|
+
);
|
|
212
|
+
} catch (e) {
|
|
213
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
214
|
+
setFontLoadError(
|
|
215
|
+
`Failed to load "${familyName}": ${msg}`,
|
|
216
|
+
);
|
|
217
|
+
addError(`Failed to load font "${familyName}": ${msg}`);
|
|
218
|
+
fontsDebug.compactError(
|
|
219
|
+
'AttributesEditor: loadFontFamily failed',
|
|
220
|
+
e,
|
|
221
|
+
{ familyName },
|
|
222
|
+
);
|
|
223
|
+
} finally {
|
|
224
|
+
setIsFontLoading(false);
|
|
225
|
+
}
|
|
226
|
+
}}
|
|
227
|
+
className={optionClassName}
|
|
228
|
+
aria-label={`Select font ${familyName}`}
|
|
229
|
+
>
|
|
230
|
+
<span
|
|
231
|
+
className="attributes-editor__font-option-name"
|
|
232
|
+
title={familyName}
|
|
233
|
+
>
|
|
234
|
+
{familyName}
|
|
235
|
+
</span>
|
|
236
|
+
<span className="attributes-editor__font-option-status">
|
|
237
|
+
{isLoaded ? 'Loaded' : 'Not loaded'}
|
|
238
|
+
</span>
|
|
239
|
+
</button>
|
|
240
|
+
);
|
|
241
|
+
})}
|
|
242
|
+
</div>
|
|
243
|
+
</div>
|
|
244
|
+
</Modal>
|
|
245
|
+
) : null}
|
|
246
|
+
</>
|
|
247
|
+
);
|
|
248
|
+
}
|
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
import React, { useMemo } from 'react';
|
|
2
|
+
import { Field } from './Field';
|
|
3
|
+
import { SpecialCategorySection } from './SpecialCategorySection';
|
|
4
|
+
import { FieldInfoTooltip } from './FieldInfoTooltip';
|
|
5
|
+
import { toPreferredScale } from './SizeField';
|
|
6
|
+
import { isBooleanFieldType, type SchemaEntry } from './types';
|
|
7
|
+
import { MockableFeatureModal } from '../modals';
|
|
8
|
+
import {
|
|
9
|
+
FontFamilyPickerField,
|
|
10
|
+
IconTypePickerField,
|
|
11
|
+
} from './AttributesEditorFields';
|
|
12
|
+
import type {
|
|
13
|
+
AttributesEditorModel,
|
|
14
|
+
AttributesEditorSpecialSection,
|
|
15
|
+
} from './attributesEditorModelTypes';
|
|
16
|
+
import type { NodeDefaultAttribute } from '../types/Node';
|
|
17
|
+
|
|
18
|
+
type AttributesEditorViewProps = AttributesEditorModel;
|
|
19
|
+
|
|
20
|
+
function getPreferredOrderedSizeEntries(entries: SchemaEntry[]): SchemaEntry[] {
|
|
21
|
+
const preferredOrder = [
|
|
22
|
+
'width',
|
|
23
|
+
'height',
|
|
24
|
+
'minWidth',
|
|
25
|
+
'minHeight',
|
|
26
|
+
'maxWidth',
|
|
27
|
+
'maxHeight',
|
|
28
|
+
];
|
|
29
|
+
return [...entries].sort((a, b) => {
|
|
30
|
+
const aIndex = preferredOrder.indexOf(a.name);
|
|
31
|
+
const bIndex = preferredOrder.indexOf(b.name);
|
|
32
|
+
const aRank = aIndex === -1 ? Number.MAX_SAFE_INTEGER : aIndex;
|
|
33
|
+
const bRank = bIndex === -1 ? Number.MAX_SAFE_INTEGER : bIndex;
|
|
34
|
+
return aRank !== bRank ? aRank - bRank : a.name.localeCompare(b.name);
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function getSpecialSectionTitle(section: AttributesEditorSpecialSection): {
|
|
39
|
+
title: string;
|
|
40
|
+
description?: string;
|
|
41
|
+
} {
|
|
42
|
+
const key = section.key;
|
|
43
|
+
const meta = section.meta;
|
|
44
|
+
const title =
|
|
45
|
+
meta?.label && meta.label.trim().length > 0
|
|
46
|
+
? meta.label
|
|
47
|
+
: key.charAt(0).toUpperCase() + key.slice(1);
|
|
48
|
+
return { title, description: meta?.description };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function AttributesEditorView(props: AttributesEditorViewProps) {
|
|
52
|
+
const {
|
|
53
|
+
isInvalidNode,
|
|
54
|
+
componentTitle,
|
|
55
|
+
componentDescription,
|
|
56
|
+
mockableFeatureKeys,
|
|
57
|
+
activeMockableFeature,
|
|
58
|
+
setActiveMockableFeature,
|
|
59
|
+
tabs,
|
|
60
|
+
tabContentInfo,
|
|
61
|
+
activeTab,
|
|
62
|
+
setActiveTab,
|
|
63
|
+
firstAvailableTab,
|
|
64
|
+
activeEntries,
|
|
65
|
+
activeSpecialSections,
|
|
66
|
+
hasStringChildren,
|
|
67
|
+
childrenValue,
|
|
68
|
+
handleChildrenChange,
|
|
69
|
+
attributeMeta,
|
|
70
|
+
data,
|
|
71
|
+
getAttributeValue,
|
|
72
|
+
handleAttributeChange,
|
|
73
|
+
projectColorsForPicker,
|
|
74
|
+
layoutContext,
|
|
75
|
+
viewAttributes,
|
|
76
|
+
styleBag,
|
|
77
|
+
attributes,
|
|
78
|
+
projectFonts,
|
|
79
|
+
loadedFonts,
|
|
80
|
+
markFontLoaded,
|
|
81
|
+
addError,
|
|
82
|
+
} = props;
|
|
83
|
+
|
|
84
|
+
const headerSection = (
|
|
85
|
+
<div className="attributes-editor__component-meta">
|
|
86
|
+
<p className="attributes-editor__component-title">{componentTitle}</p>
|
|
87
|
+
{componentDescription ? (
|
|
88
|
+
<p className="attributes-editor__component-description">
|
|
89
|
+
{componentDescription}
|
|
90
|
+
</p>
|
|
91
|
+
) : null}
|
|
92
|
+
</div>
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
const mockableSection =
|
|
96
|
+
mockableFeatureKeys.length > 0 ? (
|
|
97
|
+
<section className="attributes-editor__mockable">
|
|
98
|
+
<p className="attributes-editor__mockable-title">Mockable</p>
|
|
99
|
+
<table className="attributes-editor__mockable-table">
|
|
100
|
+
<tbody>
|
|
101
|
+
{mockableFeatureKeys.map((key) => (
|
|
102
|
+
<tr key={key} className="attributes-editor__mockable-row">
|
|
103
|
+
<td className="attributes-editor__mockable-name">{key}</td>
|
|
104
|
+
<td className="attributes-editor__mockable-action">
|
|
105
|
+
<button
|
|
106
|
+
type="button"
|
|
107
|
+
className="editor-button"
|
|
108
|
+
onClick={() => setActiveMockableFeature(key)}
|
|
109
|
+
>
|
|
110
|
+
{key}
|
|
111
|
+
</button>
|
|
112
|
+
</td>
|
|
113
|
+
</tr>
|
|
114
|
+
))}
|
|
115
|
+
</tbody>
|
|
116
|
+
</table>
|
|
117
|
+
</section>
|
|
118
|
+
) : null;
|
|
119
|
+
|
|
120
|
+
const tabsSection = (
|
|
121
|
+
<div className="attributes-editor__tabs">
|
|
122
|
+
{tabs.map((tab) => {
|
|
123
|
+
const isActive = tab.id === activeTab;
|
|
124
|
+
const counts = tabContentInfo[tab.id];
|
|
125
|
+
const totalCount = counts.baseCount + counts.specialCount;
|
|
126
|
+
const disabled = totalCount === 0;
|
|
127
|
+
const buttonClassNames = [
|
|
128
|
+
'attributes-editor__tab-button',
|
|
129
|
+
isActive ? 'attributes-editor__tab-button--active' : '',
|
|
130
|
+
]
|
|
131
|
+
.filter(Boolean)
|
|
132
|
+
.join(' ');
|
|
133
|
+
|
|
134
|
+
return (
|
|
135
|
+
<button
|
|
136
|
+
key={tab.id}
|
|
137
|
+
type="button"
|
|
138
|
+
onClick={() => !disabled && setActiveTab(tab.id)}
|
|
139
|
+
disabled={disabled}
|
|
140
|
+
className={buttonClassNames}
|
|
141
|
+
>
|
|
142
|
+
{tab.label}
|
|
143
|
+
{totalCount > 0 ? ` (${totalCount})` : ''}
|
|
144
|
+
</button>
|
|
145
|
+
);
|
|
146
|
+
})}
|
|
147
|
+
</div>
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
const childrenSection = hasStringChildren ? (
|
|
151
|
+
<div className="attributes-editor__field-wrapper attributes-editor__field-wrapper--children">
|
|
152
|
+
<p className="attributes-editor__field-label">Text</p>
|
|
153
|
+
<input
|
|
154
|
+
type="text"
|
|
155
|
+
className="attributes-editor__text-input"
|
|
156
|
+
value={childrenValue}
|
|
157
|
+
onChange={(e) => handleChildrenChange(e.target.value)}
|
|
158
|
+
/>
|
|
159
|
+
</div>
|
|
160
|
+
) : null;
|
|
161
|
+
|
|
162
|
+
const hasAnyContent = useMemo(() => {
|
|
163
|
+
const counts = tabContentInfo[firstAvailableTab];
|
|
164
|
+
return (
|
|
165
|
+
(counts?.baseCount ?? 0) + (counts?.specialCount ?? 0) > 0 ||
|
|
166
|
+
(hasStringChildren && childrenValue !== undefined)
|
|
167
|
+
);
|
|
168
|
+
}, [childrenValue, firstAvailableTab, hasStringChildren, tabContentInfo]);
|
|
169
|
+
|
|
170
|
+
function renderEntry(entry: SchemaEntry) {
|
|
171
|
+
const name = entry.name;
|
|
172
|
+
const type = entry.type;
|
|
173
|
+
const label = attributeMeta?.[name]?.label ?? name;
|
|
174
|
+
const description = attributeMeta?.[name]?.description;
|
|
175
|
+
const preferredScale = toPreferredScale(
|
|
176
|
+
attributeMeta?.[name]?.preferedScale,
|
|
177
|
+
);
|
|
178
|
+
const isBoolean = isBooleanFieldType(type);
|
|
179
|
+
const wrapperClassNames = [
|
|
180
|
+
'attributes-editor__field-wrapper',
|
|
181
|
+
isBoolean ? 'attributes-editor__field-wrapper--boolean' : '',
|
|
182
|
+
]
|
|
183
|
+
.filter(Boolean)
|
|
184
|
+
.join(' ');
|
|
185
|
+
|
|
186
|
+
const value = getAttributeValue(name);
|
|
187
|
+
|
|
188
|
+
return (
|
|
189
|
+
<FieldInfoTooltip key={name} description={description}>
|
|
190
|
+
<div className={wrapperClassNames}>
|
|
191
|
+
{!isBoolean ? (
|
|
192
|
+
<p className="attributes-editor__field-label">{label}</p>
|
|
193
|
+
) : null}
|
|
194
|
+
|
|
195
|
+
{type === 'iconType' ? (
|
|
196
|
+
<IconTypePickerField
|
|
197
|
+
name={name}
|
|
198
|
+
value={value}
|
|
199
|
+
onChange={(next) => handleAttributeChange(name, next)}
|
|
200
|
+
/>
|
|
201
|
+
) : type === 'fontFamily' ? (
|
|
202
|
+
<FontFamilyPickerField
|
|
203
|
+
name={name}
|
|
204
|
+
value={value}
|
|
205
|
+
onChange={(next) => handleAttributeChange(name, next)}
|
|
206
|
+
fonts={projectFonts}
|
|
207
|
+
loadedFonts={loadedFonts}
|
|
208
|
+
markFontLoaded={markFontLoaded}
|
|
209
|
+
addError={addError}
|
|
210
|
+
/>
|
|
211
|
+
) : (
|
|
212
|
+
<Field
|
|
213
|
+
name={name}
|
|
214
|
+
type={type}
|
|
215
|
+
value={value}
|
|
216
|
+
onChange={(val) => handleAttributeChange(name, val)}
|
|
217
|
+
componentType={data?.type}
|
|
218
|
+
projectColors={projectColorsForPicker}
|
|
219
|
+
layoutContext={layoutContext}
|
|
220
|
+
viewAttributes={viewAttributes}
|
|
221
|
+
label={isBoolean ? label : undefined}
|
|
222
|
+
preferredScale={preferredScale}
|
|
223
|
+
/>
|
|
224
|
+
)}
|
|
225
|
+
</div>
|
|
226
|
+
</FieldInfoTooltip>
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function renderSpecialSection(section: AttributesEditorSpecialSection) {
|
|
231
|
+
if (section.key === 'size') {
|
|
232
|
+
const { title, description } = getSpecialSectionTitle(section);
|
|
233
|
+
const orderedEntries = getPreferredOrderedSizeEntries(section.entries);
|
|
234
|
+
return (
|
|
235
|
+
<section key={section.key} className="special-category-section">
|
|
236
|
+
<div className="special-category-section__header">
|
|
237
|
+
<p className="special-category-section__title">{title}</p>
|
|
238
|
+
</div>
|
|
239
|
+
{description ? (
|
|
240
|
+
<p className="special-category-section__description">
|
|
241
|
+
{description}
|
|
242
|
+
</p>
|
|
243
|
+
) : null}
|
|
244
|
+
<div className="attributes-editor__size-grid">
|
|
245
|
+
{orderedEntries.map((entry) => {
|
|
246
|
+
const label = attributeMeta?.[entry.name]?.label ?? entry.name;
|
|
247
|
+
const description = attributeMeta?.[entry.name]?.description;
|
|
248
|
+
const preferredScale = toPreferredScale(
|
|
249
|
+
attributeMeta?.[entry.name]?.preferedScale,
|
|
250
|
+
);
|
|
251
|
+
const currentValue = getAttributeValue(entry.name);
|
|
252
|
+
const isBoolean = isBooleanFieldType(entry.type);
|
|
253
|
+
const wrapperClassNames = [
|
|
254
|
+
'attributes-editor__field-wrapper',
|
|
255
|
+
isBoolean ? 'attributes-editor__field-wrapper--boolean' : '',
|
|
256
|
+
]
|
|
257
|
+
.filter(Boolean)
|
|
258
|
+
.join(' ');
|
|
259
|
+
return (
|
|
260
|
+
<FieldInfoTooltip key={entry.name} description={description}>
|
|
261
|
+
<div
|
|
262
|
+
className={`${wrapperClassNames} attributes-editor__size-grid-item`}
|
|
263
|
+
>
|
|
264
|
+
{!isBoolean ? (
|
|
265
|
+
<p className="attributes-editor__field-label">{label}</p>
|
|
266
|
+
) : null}
|
|
267
|
+
{entry.type === 'iconType' ? (
|
|
268
|
+
<IconTypePickerField
|
|
269
|
+
name={entry.name}
|
|
270
|
+
value={currentValue}
|
|
271
|
+
onChange={(next) =>
|
|
272
|
+
handleAttributeChange(entry.name, next)
|
|
273
|
+
}
|
|
274
|
+
/>
|
|
275
|
+
) : entry.type === 'fontFamily' ? (
|
|
276
|
+
<FontFamilyPickerField
|
|
277
|
+
name={entry.name}
|
|
278
|
+
value={currentValue}
|
|
279
|
+
onChange={(next) =>
|
|
280
|
+
handleAttributeChange(entry.name, next)
|
|
281
|
+
}
|
|
282
|
+
fonts={projectFonts}
|
|
283
|
+
loadedFonts={loadedFonts}
|
|
284
|
+
markFontLoaded={markFontLoaded}
|
|
285
|
+
addError={addError}
|
|
286
|
+
/>
|
|
287
|
+
) : (
|
|
288
|
+
<Field
|
|
289
|
+
name={entry.name}
|
|
290
|
+
type={entry.type}
|
|
291
|
+
value={currentValue}
|
|
292
|
+
onChange={(val) =>
|
|
293
|
+
handleAttributeChange(entry.name, val)
|
|
294
|
+
}
|
|
295
|
+
componentType={data?.type}
|
|
296
|
+
projectColors={projectColorsForPicker}
|
|
297
|
+
layoutContext={layoutContext}
|
|
298
|
+
viewAttributes={viewAttributes}
|
|
299
|
+
label={isBoolean ? label : undefined}
|
|
300
|
+
preferredScale={preferredScale}
|
|
301
|
+
/>
|
|
302
|
+
)}
|
|
303
|
+
</div>
|
|
304
|
+
</FieldInfoTooltip>
|
|
305
|
+
);
|
|
306
|
+
})}
|
|
307
|
+
</div>
|
|
308
|
+
</section>
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const sectionAttributes =
|
|
313
|
+
section.meta?.category === 'style'
|
|
314
|
+
? ((styleBag ?? {}) as unknown as NodeDefaultAttribute)
|
|
315
|
+
: attributes;
|
|
316
|
+
|
|
317
|
+
return (
|
|
318
|
+
<SpecialCategorySection
|
|
319
|
+
key={section.key}
|
|
320
|
+
category={section.key}
|
|
321
|
+
entries={section.entries}
|
|
322
|
+
attributeMeta={attributeMeta}
|
|
323
|
+
attributes={sectionAttributes}
|
|
324
|
+
onAttributeChange={handleAttributeChange}
|
|
325
|
+
componentType={data?.type}
|
|
326
|
+
projectColors={projectColorsForPicker}
|
|
327
|
+
layoutContext={layoutContext}
|
|
328
|
+
viewAttributes={viewAttributes}
|
|
329
|
+
meta={section.meta}
|
|
330
|
+
/>
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (isInvalidNode) return null;
|
|
335
|
+
|
|
336
|
+
return (
|
|
337
|
+
<div className="attributes-editor">
|
|
338
|
+
{headerSection}
|
|
339
|
+
{mockableSection}
|
|
340
|
+
{tabsSection}
|
|
341
|
+
{childrenSection}
|
|
342
|
+
{activeSpecialSections.map(renderSpecialSection)}
|
|
343
|
+
|
|
344
|
+
{activeEntries.map(renderEntry)}
|
|
345
|
+
|
|
346
|
+
{!hasAnyContent ? (
|
|
347
|
+
<div className="attributes-editor__empty-state">
|
|
348
|
+
No editable attributes
|
|
349
|
+
</div>
|
|
350
|
+
) : null}
|
|
351
|
+
|
|
352
|
+
{activeMockableFeature ? (
|
|
353
|
+
<MockableFeatureModal
|
|
354
|
+
featureKey={activeMockableFeature}
|
|
355
|
+
onClose={() => setActiveMockableFeature(null)}
|
|
356
|
+
/>
|
|
357
|
+
) : null}
|
|
358
|
+
</div>
|
|
359
|
+
);
|
|
360
|
+
}
|
|
@@ -94,12 +94,13 @@ function LayoutPreview({
|
|
|
94
94
|
const resolved = resolvePreviewContext(mode, option, layoutContext);
|
|
95
95
|
const isColumn = resolved.flexDirection?.startsWith('column');
|
|
96
96
|
const resolvedBackground =
|
|
97
|
-
viewAttributes?.backgroundColor ??
|
|
97
|
+
(viewAttributes as any)?.style?.backgroundColor ??
|
|
98
98
|
(isActive
|
|
99
99
|
? 'hsl(var(--muted, var(--rb-muted, 220 14.3% 95.9%)) / 0.55)'
|
|
100
100
|
: 'hsl(var(--muted, var(--rb-muted, 220 14.3% 95.9%)))');
|
|
101
|
-
const resolvedGap = parseNumeric(viewAttributes?.gap) ?? 3;
|
|
102
|
-
const resolvedBorderRadius =
|
|
101
|
+
const resolvedGap = parseNumeric((viewAttributes as any)?.style?.gap) ?? 3;
|
|
102
|
+
const resolvedBorderRadius =
|
|
103
|
+
parseNumeric((viewAttributes as any)?.style?.borderRadius) ?? 4;
|
|
103
104
|
|
|
104
105
|
const stretchOverrides =
|
|
105
106
|
resolved.alignItems === 'stretch'
|