@developer_tribe/react-builder 1.2.39 → 1.2.41
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/attributes-editor/FallbackLocalizationField.d.ts +6 -0
- package/dist/build-components/NavigationBarColor/NavigationBarColorProps.generated.d.ts +1 -40
- package/dist/build-components/StatusBarColor/StatusBarColorProps.generated.d.ts +1 -1
- package/dist/build-components/patterns.generated.d.ts +21 -344
- package/dist/components/BuilderProvider.d.ts +1 -0
- package/dist/components/DeviceButton.d.ts +4 -1
- package/dist/index.cjs.js +1 -1
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.esm.js +1 -1
- package/dist/index.esm.js.map +1 -1
- package/dist/index.web.cjs.js +4 -4
- package/dist/index.web.cjs.js.map +1 -1
- package/dist/index.web.d.ts +8 -0
- package/dist/index.web.esm.js +4 -4
- package/dist/index.web.esm.js.map +1 -1
- package/dist/mockOS/context/MockOSContext.d.ts +3 -1
- package/dist/product-base/types.d.ts +3 -0
- package/dist/size-matters/index.d.ts +1 -1
- package/dist/store.d.ts +31 -0
- package/dist/styles.css +1 -1
- package/dist/types/Device.d.ts +5 -0
- package/dist/types/PreviewConfig.d.ts +1 -1
- package/dist/utils/extractTextStyle/extractTextStyle.d.ts +1 -0
- package/dist/utils/extractViewStyle/extractViewStyle.d.ts +1 -0
- package/package.json +1 -1
- package/scripts/prebuild/assets/prompt_scheme.md +7 -0
- package/scripts/public/bin.js +0 -0
- package/src/DeviceMockFrame.tsx +8 -0
- package/src/RenderPage.tsx +3 -0
- package/src/assets/devices.json +747 -183
- package/src/assets/meta.json +1 -1
- package/src/assets/prompt-scheme-onboard.generated.ts +1 -1
- package/src/assets/prompt-scheme-paywall.generated.ts +1 -1
- package/src/assets/samples/carousel-sample.json +30 -26
- package/src/assets/samples/paywall-1.json +31 -31
- package/src/assets/samples/paywall-2.json +28 -28
- package/src/assets/samples/paywall-app-delete-offer.json +29 -29
- package/src/assets/samples/paywall-app-open-offer.json +29 -29
- package/src/assets/samples/paywall-back-offer.json +28 -28
- package/src/assets/samples/paywall-notification-offer.json +28 -28
- package/src/assets/samples/simple-1.json +4 -4
- package/src/assets/samples/simple-2.json +25 -25
- package/src/assets/samples/unmigrated-builder-1.1.1.json +7 -7
- package/src/assets/samples/unmigrated-builder1.json +4 -4
- package/src/assets/samples/unvalidated-builder1.json +4 -4
- package/src/assets/samples/unvalidated-crash1.json +2 -2
- package/src/assets/samples/unvalidated-crashcomponent1.json +2 -2
- package/src/assets/samples/vpn-onboard-1.json +30 -30
- package/src/assets/samples/vpn-onboard-2.json +30 -30
- package/src/assets/samples/vpn-onboard-3.json +27 -27
- package/src/assets/samples/vpn-onboard-4.json +27 -27
- package/src/assets/samples/vpn-onboard-5.json +40 -40
- package/src/assets/samples/vpn-onboard-6.json +30 -30
- package/src/assets/samples/vpn-onboard-7.json +29 -29
- package/src/attribute-analyser/style/web/useExtractImageStyle.ts +8 -3
- package/src/attribute-analyser/style/web/useExtractViewStyle.ts +8 -3
- package/src/attributes-editor/AttributesEditorView.tsx +17 -6
- package/src/attributes-editor/FallbackLocalizationField.tsx +384 -0
- package/src/build-components/CarouselDots/CarouselDots.tsx +8 -3
- package/src/build-components/Main/Main.tsx +3 -1
- package/src/build-components/NavigationBarColor/NavigationBarColor.tsx +15 -1
- package/src/build-components/NavigationBarColor/NavigationBarColorProps.generated.ts +1 -52
- package/src/build-components/NavigationBarColor/pattern.json +11 -2
- package/src/build-components/OnboardDot/OnboardDot.tsx +3 -2
- package/src/build-components/PaywallCloseButton/pattern.json +1 -0
- package/src/build-components/StatusBarColor/StatusBarColor.tsx +15 -1
- package/src/build-components/StatusBarColor/StatusBarColorProps.generated.ts +1 -1
- package/src/build-components/StatusBarColor/pattern.json +10 -1
- package/src/build-components/patterns.generated.ts +25 -364
- package/src/components/BottomBar.tsx +135 -31
- package/src/components/BuilderProvider.tsx +1 -0
- package/src/components/DeviceButton.tsx +35 -0
- package/src/components/EditorHeader.tsx +16 -1
- package/src/hooks/useLocalize.ts +3 -1
- package/src/hooks/useSafeAreaViewStyle.ts +24 -4
- package/src/index.web.ts +19 -0
- package/src/mockOS/context/MockOSContext.tsx +41 -13
- package/src/modals/DeviceSelectorModal.tsx +94 -10
- package/src/modals/InspectModal.tsx +112 -4
- package/src/product-base/buildPaywallLocalizationParams.ts +3 -0
- package/src/product-base/extractAndroidParams.ts +38 -8
- package/src/product-base/types.ts +3 -0
- package/src/size-matters/index.ts +15 -9
- package/src/store.ts +66 -0
- package/src/styles/modals/_product-edit-modal.scss +2 -2
- package/src/types/Device.ts +5 -0
- package/src/types/PreviewConfig.ts +6 -0
- package/src/utils/analyseNodeByPatterns.ts +6 -2
- package/src/utils/extractTextStyle/extractTextStyle.ts +3 -1
- package/src/utils/extractTextStyle/extractTextStyleNative.ts +1 -1
- package/src/utils/extractViewStyle/extractViewStyle.ts +19 -5
- package/src/utils/extractViewStyle/extractViewStyleNative.ts +5 -1
- package/src/utils/replaceLocalizationParams.ts +5 -7
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
import React, { useState, useCallback, useRef, useEffect } from 'react';
|
|
2
|
+
import { useRenderStore } from '../store';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Self-contained fallback localization field for react-builder.
|
|
6
|
+
*
|
|
7
|
+
* Uses the localizationApiConfig from the store to:
|
|
8
|
+
* 1. Pre-load ALL keys on first open (per_page=100, auto-paginate)
|
|
9
|
+
* 2. Filter locally — no subsequent API calls for search
|
|
10
|
+
* 3. Works independently of the host app
|
|
11
|
+
*
|
|
12
|
+
* API: GET {forgeUrl}/api/manage/localization/{platform}?per_page=100&page=N&app_id=X
|
|
13
|
+
* Header: X-Authorization: {forgeToken}
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
type LocalizationRecord = {
|
|
17
|
+
key: string;
|
|
18
|
+
translations: Record<string, { value: string | null }>;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
type FallbackLocalizationFieldProps = {
|
|
22
|
+
value: string;
|
|
23
|
+
onChange: (v: string) => void;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export function FallbackLocalizationField({
|
|
27
|
+
value,
|
|
28
|
+
onChange,
|
|
29
|
+
}: FallbackLocalizationFieldProps) {
|
|
30
|
+
const config = useRenderStore((s) => s.localizationApiConfig);
|
|
31
|
+
const [open, setOpen] = useState(false);
|
|
32
|
+
const [query, setQuery] = useState('');
|
|
33
|
+
const [allRecords, setAllRecords] = useState<LocalizationRecord[]>([]);
|
|
34
|
+
const [filteredRecords, setFilteredRecords] = useState<LocalizationRecord[]>(
|
|
35
|
+
[],
|
|
36
|
+
);
|
|
37
|
+
const [preloadDone, setPreloadDone] = useState(false);
|
|
38
|
+
const [loading, setLoading] = useState(false);
|
|
39
|
+
const preloadVersionRef = useRef(0);
|
|
40
|
+
|
|
41
|
+
const canSearch = !!config?.forgeUrl && !!config?.forgeToken;
|
|
42
|
+
const platform = 'ios'; // default platform for fallback
|
|
43
|
+
|
|
44
|
+
// --- Forge API helpers ---
|
|
45
|
+
const buildUrl = useCallback(
|
|
46
|
+
(page: number) => {
|
|
47
|
+
if (!config) return '';
|
|
48
|
+
const base = `${config.forgeUrl.replace(/\/$/, '')}/api/manage/localization/${platform}`;
|
|
49
|
+
const params = new URLSearchParams({
|
|
50
|
+
per_page: '1000',
|
|
51
|
+
page: String(page),
|
|
52
|
+
});
|
|
53
|
+
if (config.forgeAppId) {
|
|
54
|
+
params.append('app_id', String(config.forgeAppId));
|
|
55
|
+
}
|
|
56
|
+
return `${base}?${params.toString()}`;
|
|
57
|
+
},
|
|
58
|
+
[config],
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
const fetchPage = useCallback(
|
|
62
|
+
async (page: number) => {
|
|
63
|
+
const url = buildUrl(page);
|
|
64
|
+
const res = await fetch(url, {
|
|
65
|
+
headers: {
|
|
66
|
+
'Content-Type': 'application/json',
|
|
67
|
+
Accept: 'application/json',
|
|
68
|
+
'X-Authorization': config!.forgeToken,
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
if (!res.ok) return { data: [] as LocalizationRecord[], hasMore: false };
|
|
72
|
+
const json = await res.json();
|
|
73
|
+
const records: LocalizationRecord[] = json.data ?? [];
|
|
74
|
+
const pagination = json.pagination;
|
|
75
|
+
const hasMore =
|
|
76
|
+
pagination && pagination.current_page < pagination.last_page;
|
|
77
|
+
return { data: records, hasMore };
|
|
78
|
+
},
|
|
79
|
+
[buildUrl, config],
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
// --- Pre-load all records on first dialog open ---
|
|
83
|
+
useEffect(() => {
|
|
84
|
+
if (!open || !canSearch || preloadDone) return;
|
|
85
|
+
|
|
86
|
+
const version = ++preloadVersionRef.current;
|
|
87
|
+
setLoading(true);
|
|
88
|
+
|
|
89
|
+
const loadAll = async () => {
|
|
90
|
+
const all: LocalizationRecord[] = [];
|
|
91
|
+
let page = 1;
|
|
92
|
+
let hasMore = true;
|
|
93
|
+
while (hasMore) {
|
|
94
|
+
const result = await fetchPage(page);
|
|
95
|
+
if (version !== preloadVersionRef.current) return;
|
|
96
|
+
all.push(...result.data);
|
|
97
|
+
hasMore = result.hasMore;
|
|
98
|
+
page++;
|
|
99
|
+
}
|
|
100
|
+
if (version === preloadVersionRef.current) {
|
|
101
|
+
setAllRecords(all);
|
|
102
|
+
setFilteredRecords(all);
|
|
103
|
+
setPreloadDone(true);
|
|
104
|
+
setLoading(false);
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
void loadAll();
|
|
109
|
+
}, [open, canSearch, preloadDone, fetchPage]);
|
|
110
|
+
|
|
111
|
+
// --- Local filter ---
|
|
112
|
+
useEffect(() => {
|
|
113
|
+
if (!preloadDone) return;
|
|
114
|
+
const q = query.trim().toLowerCase();
|
|
115
|
+
if (!q) {
|
|
116
|
+
setFilteredRecords(allRecords);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
setFilteredRecords(
|
|
120
|
+
allRecords.filter((r) => {
|
|
121
|
+
if (r.key.toLowerCase().includes(q)) return true;
|
|
122
|
+
return Object.values(r.translations).some(
|
|
123
|
+
(t) => t?.value && t.value.toLowerCase().includes(q),
|
|
124
|
+
);
|
|
125
|
+
}),
|
|
126
|
+
);
|
|
127
|
+
}, [query, allRecords, preloadDone]);
|
|
128
|
+
|
|
129
|
+
const invalidate = () => {
|
|
130
|
+
setPreloadDone(false);
|
|
131
|
+
setAllRecords([]);
|
|
132
|
+
setFilteredRecords([]);
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
return (
|
|
136
|
+
<>
|
|
137
|
+
<div style={styles.row}>
|
|
138
|
+
<input
|
|
139
|
+
type="text"
|
|
140
|
+
className="attributes-editor__text-input"
|
|
141
|
+
value={value ?? ''}
|
|
142
|
+
onChange={(e) => onChange(e.target.value)}
|
|
143
|
+
style={{ flex: 1 }}
|
|
144
|
+
/>
|
|
145
|
+
{canSearch ? (
|
|
146
|
+
<button
|
|
147
|
+
type="button"
|
|
148
|
+
onClick={() => setOpen(true)}
|
|
149
|
+
title="Browse localization keys"
|
|
150
|
+
style={styles.browseBtn}
|
|
151
|
+
>
|
|
152
|
+
🔍
|
|
153
|
+
</button>
|
|
154
|
+
) : (
|
|
155
|
+
<span title="Localization API yapılandırılmadı" style={styles.dot} />
|
|
156
|
+
)}
|
|
157
|
+
</div>
|
|
158
|
+
{open && (
|
|
159
|
+
<div onClick={() => setOpen(false)} style={styles.overlay}>
|
|
160
|
+
<div onClick={(e) => e.stopPropagation()} style={styles.modal}>
|
|
161
|
+
{/* Header */}
|
|
162
|
+
<div style={styles.header}>
|
|
163
|
+
<span style={{ fontWeight: 600, fontSize: 14 }}>
|
|
164
|
+
Localization Anahtarı Seç
|
|
165
|
+
</span>
|
|
166
|
+
<button
|
|
167
|
+
type="button"
|
|
168
|
+
onClick={() => setOpen(false)}
|
|
169
|
+
style={styles.closeBtn}
|
|
170
|
+
>
|
|
171
|
+
✕
|
|
172
|
+
</button>
|
|
173
|
+
</div>
|
|
174
|
+
{/* Search */}
|
|
175
|
+
<div style={{ padding: '10px 16px 6px' }}>
|
|
176
|
+
<input
|
|
177
|
+
autoFocus
|
|
178
|
+
type="text"
|
|
179
|
+
value={query}
|
|
180
|
+
onChange={(e) => setQuery(e.target.value)}
|
|
181
|
+
placeholder="Anahtar veya çeviri ara…"
|
|
182
|
+
style={styles.searchInput}
|
|
183
|
+
/>
|
|
184
|
+
</div>
|
|
185
|
+
{/* Info */}
|
|
186
|
+
{preloadDone && (
|
|
187
|
+
<div style={styles.infoBar}>
|
|
188
|
+
{filteredRecords.length} / {allRecords.length} anahtar
|
|
189
|
+
</div>
|
|
190
|
+
)}
|
|
191
|
+
{/* Results */}
|
|
192
|
+
<div style={styles.resultsList}>
|
|
193
|
+
{loading && <div style={styles.emptyMsg}>Yükleniyor…</div>}
|
|
194
|
+
{!loading && preloadDone && filteredRecords.length === 0 && (
|
|
195
|
+
<div style={styles.emptyMsg}>Sonuç bulunamadı</div>
|
|
196
|
+
)}
|
|
197
|
+
{filteredRecords.map((entry) => {
|
|
198
|
+
const isSelected = entry.key === value;
|
|
199
|
+
const translations = Object.entries(entry.translations);
|
|
200
|
+
return (
|
|
201
|
+
<button
|
|
202
|
+
key={entry.key}
|
|
203
|
+
type="button"
|
|
204
|
+
onClick={() => {
|
|
205
|
+
onChange(entry.key);
|
|
206
|
+
setOpen(false);
|
|
207
|
+
}}
|
|
208
|
+
style={{
|
|
209
|
+
...styles.resultItem,
|
|
210
|
+
background: isSelected ? '#333' : 'transparent',
|
|
211
|
+
border: isSelected
|
|
212
|
+
? '1px solid #6366f1'
|
|
213
|
+
: '1px solid #333',
|
|
214
|
+
}}
|
|
215
|
+
onMouseEnter={(e) => {
|
|
216
|
+
if (!isSelected)
|
|
217
|
+
e.currentTarget.style.background = '#2a2a2a';
|
|
218
|
+
}}
|
|
219
|
+
onMouseLeave={(e) => {
|
|
220
|
+
if (!isSelected)
|
|
221
|
+
e.currentTarget.style.background = 'transparent';
|
|
222
|
+
}}
|
|
223
|
+
>
|
|
224
|
+
<div
|
|
225
|
+
style={{
|
|
226
|
+
fontWeight: 600,
|
|
227
|
+
fontSize: 12,
|
|
228
|
+
color: '#e0e0e0',
|
|
229
|
+
}}
|
|
230
|
+
>
|
|
231
|
+
{entry.key}
|
|
232
|
+
</div>
|
|
233
|
+
{translations.slice(0, 3).map(([lang, cell]) => (
|
|
234
|
+
<div key={lang} style={styles.translationRow}>
|
|
235
|
+
<span style={styles.langBadge}>{lang}</span>
|
|
236
|
+
<span style={styles.translationText}>
|
|
237
|
+
{cell?.value && cell.value.length > 80
|
|
238
|
+
? cell.value.slice(0, 80) + '…'
|
|
239
|
+
: cell?.value || (
|
|
240
|
+
<span style={{ color: '#ef4444' }}>Eksik</span>
|
|
241
|
+
)}
|
|
242
|
+
</span>
|
|
243
|
+
</div>
|
|
244
|
+
))}
|
|
245
|
+
</button>
|
|
246
|
+
);
|
|
247
|
+
})}
|
|
248
|
+
</div>
|
|
249
|
+
</div>
|
|
250
|
+
</div>
|
|
251
|
+
)}
|
|
252
|
+
</>
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const styles: Record<string, React.CSSProperties> = {
|
|
257
|
+
row: {
|
|
258
|
+
display: 'flex',
|
|
259
|
+
alignItems: 'center',
|
|
260
|
+
gap: 4,
|
|
261
|
+
},
|
|
262
|
+
browseBtn: {
|
|
263
|
+
width: 28,
|
|
264
|
+
height: 28,
|
|
265
|
+
display: 'flex',
|
|
266
|
+
alignItems: 'center',
|
|
267
|
+
justifyContent: 'center',
|
|
268
|
+
border: '1px solid hsl(0 0% 40%)',
|
|
269
|
+
borderRadius: 4,
|
|
270
|
+
background: '#2a2a2a',
|
|
271
|
+
color: '#fff',
|
|
272
|
+
cursor: 'pointer',
|
|
273
|
+
fontSize: 13,
|
|
274
|
+
flexShrink: 0,
|
|
275
|
+
},
|
|
276
|
+
dot: {
|
|
277
|
+
display: 'inline-block',
|
|
278
|
+
width: 8,
|
|
279
|
+
height: 8,
|
|
280
|
+
borderRadius: '50%',
|
|
281
|
+
backgroundColor: '#ef4444',
|
|
282
|
+
flexShrink: 0,
|
|
283
|
+
cursor: 'help',
|
|
284
|
+
},
|
|
285
|
+
overlay: {
|
|
286
|
+
position: 'fixed',
|
|
287
|
+
inset: 0,
|
|
288
|
+
background: 'rgba(0,0,0,0.6)',
|
|
289
|
+
zIndex: 9999,
|
|
290
|
+
display: 'flex',
|
|
291
|
+
alignItems: 'center',
|
|
292
|
+
justifyContent: 'center',
|
|
293
|
+
},
|
|
294
|
+
modal: {
|
|
295
|
+
background: '#1e1e1e',
|
|
296
|
+
borderRadius: 10,
|
|
297
|
+
width: 460,
|
|
298
|
+
maxHeight: '75vh',
|
|
299
|
+
display: 'flex',
|
|
300
|
+
flexDirection: 'column',
|
|
301
|
+
boxShadow: '0 12px 40px rgba(0,0,0,0.5)',
|
|
302
|
+
color: '#e0e0e0',
|
|
303
|
+
border: '1px solid #333',
|
|
304
|
+
overflow: 'hidden',
|
|
305
|
+
},
|
|
306
|
+
header: {
|
|
307
|
+
display: 'flex',
|
|
308
|
+
justifyContent: 'space-between',
|
|
309
|
+
alignItems: 'center',
|
|
310
|
+
padding: '14px 18px',
|
|
311
|
+
borderBottom: '1px solid #333',
|
|
312
|
+
},
|
|
313
|
+
closeBtn: {
|
|
314
|
+
background: 'none',
|
|
315
|
+
border: 'none',
|
|
316
|
+
cursor: 'pointer',
|
|
317
|
+
fontSize: 18,
|
|
318
|
+
color: '#999',
|
|
319
|
+
padding: 4,
|
|
320
|
+
},
|
|
321
|
+
searchInput: {
|
|
322
|
+
width: '100%',
|
|
323
|
+
padding: '8px 10px',
|
|
324
|
+
border: '1px solid #444',
|
|
325
|
+
borderRadius: 6,
|
|
326
|
+
background: '#2a2a2a',
|
|
327
|
+
color: '#e0e0e0',
|
|
328
|
+
fontSize: 13,
|
|
329
|
+
outline: 'none',
|
|
330
|
+
boxSizing: 'border-box' as const,
|
|
331
|
+
},
|
|
332
|
+
infoBar: {
|
|
333
|
+
padding: '2px 18px 4px',
|
|
334
|
+
fontSize: 11,
|
|
335
|
+
color: '#666',
|
|
336
|
+
textAlign: 'right' as const,
|
|
337
|
+
},
|
|
338
|
+
resultsList: {
|
|
339
|
+
overflowY: 'auto' as const,
|
|
340
|
+
flex: 1,
|
|
341
|
+
padding: '6px 10px 10px',
|
|
342
|
+
},
|
|
343
|
+
emptyMsg: {
|
|
344
|
+
padding: 20,
|
|
345
|
+
textAlign: 'center' as const,
|
|
346
|
+
color: '#888',
|
|
347
|
+
fontSize: 13,
|
|
348
|
+
},
|
|
349
|
+
resultItem: {
|
|
350
|
+
display: 'block',
|
|
351
|
+
width: '100%',
|
|
352
|
+
textAlign: 'left' as const,
|
|
353
|
+
borderRadius: 6,
|
|
354
|
+
padding: '8px 10px',
|
|
355
|
+
cursor: 'pointer',
|
|
356
|
+
marginBottom: 3,
|
|
357
|
+
transition: 'background 0.15s',
|
|
358
|
+
},
|
|
359
|
+
translationRow: {
|
|
360
|
+
display: 'flex',
|
|
361
|
+
alignItems: 'center',
|
|
362
|
+
gap: 6,
|
|
363
|
+
marginTop: 3,
|
|
364
|
+
fontSize: 11,
|
|
365
|
+
},
|
|
366
|
+
langBadge: {
|
|
367
|
+
display: 'inline-block',
|
|
368
|
+
background: '#333',
|
|
369
|
+
border: '1px solid #444',
|
|
370
|
+
borderRadius: 3,
|
|
371
|
+
padding: '1px 5px',
|
|
372
|
+
fontSize: 10,
|
|
373
|
+
fontWeight: 600,
|
|
374
|
+
color: '#aaa',
|
|
375
|
+
flexShrink: 0,
|
|
376
|
+
},
|
|
377
|
+
translationText: {
|
|
378
|
+
color: '#999',
|
|
379
|
+
overflow: 'hidden',
|
|
380
|
+
whiteSpace: 'nowrap' as const,
|
|
381
|
+
textOverflow: 'ellipsis',
|
|
382
|
+
flex: 1,
|
|
383
|
+
},
|
|
384
|
+
};
|
|
@@ -34,7 +34,12 @@ function CarouselDots({ node }: CarouselDotsComponentProps) {
|
|
|
34
34
|
const onboardApi = useContext(onboardContext);
|
|
35
35
|
const emblaApi = isOnboard ? onboardApi?.emblaApi : carouselApi;
|
|
36
36
|
|
|
37
|
-
const {
|
|
37
|
+
const {
|
|
38
|
+
selectedTheme: theme,
|
|
39
|
+
projectColors,
|
|
40
|
+
baseSize,
|
|
41
|
+
device,
|
|
42
|
+
} = useBuilderParams();
|
|
38
43
|
const inactiveDotOpacity =
|
|
39
44
|
(stylesBag?.inactive_dot_opacity as number | undefined) ??
|
|
40
45
|
(attrRecord.inactive_dot_opacity as number | undefined) ??
|
|
@@ -59,12 +64,12 @@ function CarouselDots({ node }: CarouselDotsComponentProps) {
|
|
|
59
64
|
);
|
|
60
65
|
|
|
61
66
|
const dotSizeCss = useMemo(() => {
|
|
62
|
-
const parsed = parseSize(dotThicknessRaw, baseSize);
|
|
67
|
+
const parsed = parseSize(dotThicknessRaw, baseSize, device!);
|
|
63
68
|
if (parsed === undefined) return '10px';
|
|
64
69
|
if (typeof parsed === 'number') return `${parsed}px`;
|
|
65
70
|
if (typeof parsed === 'string' && parsed.trim()) return parsed;
|
|
66
71
|
return '10px';
|
|
67
|
-
}, [dotThicknessRaw, baseSize]);
|
|
72
|
+
}, [dotThicknessRaw, baseSize, device]);
|
|
68
73
|
|
|
69
74
|
const dotGapCss = useMemo((): string => {
|
|
70
75
|
const px =
|
|
@@ -4,6 +4,7 @@ import RenderNode from '../RenderNode.generated';
|
|
|
4
4
|
import type { Node } from '../../types/Node';
|
|
5
5
|
import useNode from '../useNode';
|
|
6
6
|
import { useBuilderParams } from '../../components/BuilderProvider';
|
|
7
|
+
import { useRenderStore } from '../../store';
|
|
7
8
|
import { useExtractViewStyle } from '../../attribute-analyser/style/web/useExtractViewStyle';
|
|
8
9
|
import { useLogRender } from '../../utils/useLogRender';
|
|
9
10
|
import { isNodeSelected, SELECTED_OUTLINE_STYLE } from '../../utils/selection';
|
|
@@ -19,11 +20,12 @@ function Main({ node }: MainComponentProps) {
|
|
|
19
20
|
const attributeKey = node.key ?? generatedId;
|
|
20
21
|
|
|
21
22
|
const { previewMode, selectedKey } = useBuilderParams();
|
|
23
|
+
const device = useRenderStore((s) => s.device);
|
|
22
24
|
|
|
23
25
|
const baseStyle = useExtractViewStyle(node);
|
|
24
26
|
|
|
25
27
|
const useSafeAreaView = node.attributes?.useSafeAreaView ?? true;
|
|
26
|
-
const layoutStyle = useSafeAreaViewStyle(baseStyle, useSafeAreaView);
|
|
28
|
+
const layoutStyle = useSafeAreaViewStyle(baseStyle, useSafeAreaView, device);
|
|
27
29
|
|
|
28
30
|
const isSelected = isNodeSelected({
|
|
29
31
|
previewMode: !!previewMode,
|
|
@@ -15,6 +15,9 @@ function NavigationBarColor({ node }: NavigationBarColorComponentProps) {
|
|
|
15
15
|
const setNavBarOverrideColor = useRenderStore(
|
|
16
16
|
(s) => s.setNavBarOverrideColor,
|
|
17
17
|
);
|
|
18
|
+
const setNavBarOverrideTranslucent = useRenderStore(
|
|
19
|
+
(s) => s.setNavBarOverrideTranslucent,
|
|
20
|
+
);
|
|
18
21
|
|
|
19
22
|
const rawBg = getStyleBag(node.attributes)?.backgroundColor as
|
|
20
23
|
| string
|
|
@@ -24,14 +27,25 @@ function NavigationBarColor({ node }: NavigationBarColorComponentProps) {
|
|
|
24
27
|
theme,
|
|
25
28
|
});
|
|
26
29
|
|
|
30
|
+
const translucent = node.attributes?.translucent;
|
|
31
|
+
|
|
27
32
|
useEffect(() => {
|
|
28
33
|
if (resolvedColor) {
|
|
29
34
|
setNavBarOverrideColor(resolvedColor);
|
|
30
35
|
}
|
|
36
|
+
if (translucent !== undefined) {
|
|
37
|
+
setNavBarOverrideTranslucent(translucent);
|
|
38
|
+
}
|
|
31
39
|
return () => {
|
|
32
40
|
setNavBarOverrideColor(null);
|
|
41
|
+
setNavBarOverrideTranslucent(null);
|
|
33
42
|
};
|
|
34
|
-
}, [
|
|
43
|
+
}, [
|
|
44
|
+
resolvedColor,
|
|
45
|
+
setNavBarOverrideColor,
|
|
46
|
+
translucent,
|
|
47
|
+
setNavBarOverrideTranslucent,
|
|
48
|
+
]);
|
|
35
49
|
|
|
36
50
|
return null;
|
|
37
51
|
}
|
|
@@ -2,66 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
import type { NodeData } from '../../types/Node';
|
|
4
4
|
|
|
5
|
-
export type FlexDirectionOptionType = 'row' | 'column';
|
|
6
|
-
export type FlexWrapOptionType = 'nowrap' | 'wrap' | 'wrap-reverse';
|
|
7
|
-
export type AlignItemsOptionType =
|
|
8
|
-
| 'flex-start'
|
|
9
|
-
| 'center'
|
|
10
|
-
| 'flex-end'
|
|
11
|
-
| 'stretch'
|
|
12
|
-
| 'baseline';
|
|
13
|
-
export type JustifyContentOptionType =
|
|
14
|
-
| 'flex-start'
|
|
15
|
-
| 'center'
|
|
16
|
-
| 'flex-end'
|
|
17
|
-
| 'space-between'
|
|
18
|
-
| 'space-around'
|
|
19
|
-
| 'space-evenly';
|
|
20
|
-
export type PositionOptionType = 'relative' | 'absolute';
|
|
21
|
-
|
|
22
5
|
export interface NavigationBarColorStyleGenerated {
|
|
23
|
-
flexDirection?: FlexDirectionOptionType;
|
|
24
|
-
flexWrap?: FlexWrapOptionType;
|
|
25
|
-
alignItems?: AlignItemsOptionType;
|
|
26
|
-
justifyContent?: JustifyContentOptionType;
|
|
27
|
-
gap?: string;
|
|
28
|
-
padding?: string;
|
|
29
|
-
paddingHorizontal?: string;
|
|
30
|
-
paddingVertical?: string;
|
|
31
|
-
paddingTop?: string;
|
|
32
|
-
paddingBottom?: string;
|
|
33
|
-
paddingLeft?: string;
|
|
34
|
-
paddingRight?: string;
|
|
35
|
-
margin?: string;
|
|
36
|
-
marginHorizontal?: string;
|
|
37
|
-
marginVertical?: string;
|
|
38
|
-
marginTop?: string;
|
|
39
|
-
marginBottom?: string;
|
|
40
|
-
marginLeft?: string;
|
|
41
|
-
marginRight?: string;
|
|
42
6
|
backgroundColor?: string;
|
|
43
|
-
borderRadius?: string;
|
|
44
|
-
width?: string;
|
|
45
|
-
minWidth?: string;
|
|
46
|
-
maxWidth?: string;
|
|
47
|
-
height?: string;
|
|
48
|
-
minHeight?: string;
|
|
49
|
-
maxHeight?: string;
|
|
50
|
-
flex?: number;
|
|
51
|
-
position?: PositionOptionType;
|
|
52
|
-
top?: string;
|
|
53
|
-
bottom?: string;
|
|
54
|
-
left?: string;
|
|
55
|
-
right?: string;
|
|
56
|
-
zIndex?: number;
|
|
57
7
|
}
|
|
58
8
|
|
|
59
9
|
export interface NavigationBarColorPropsGenerated {
|
|
60
10
|
child: string;
|
|
61
11
|
attributes: {
|
|
62
12
|
styles?: NavigationBarColorStyleGenerated;
|
|
63
|
-
|
|
64
|
-
testID?: string;
|
|
13
|
+
translucent?: boolean;
|
|
65
14
|
};
|
|
66
15
|
}
|
|
67
16
|
|
|
@@ -5,11 +5,11 @@
|
|
|
5
5
|
"title": "title",
|
|
6
6
|
"description": "description",
|
|
7
7
|
"children": "never",
|
|
8
|
-
"extends": "View",
|
|
9
8
|
"attributes": {
|
|
10
9
|
"styles": {
|
|
11
10
|
"backgroundColor": "color"
|
|
12
|
-
}
|
|
11
|
+
},
|
|
12
|
+
"translucent": "boolean"
|
|
13
13
|
}
|
|
14
14
|
},
|
|
15
15
|
"meta": {
|
|
@@ -24,6 +24,15 @@
|
|
|
24
24
|
"specialCategory": null,
|
|
25
25
|
"sort": 1
|
|
26
26
|
}
|
|
27
|
+
},
|
|
28
|
+
"attributes": {
|
|
29
|
+
"translucent": {
|
|
30
|
+
"label": "Translucent",
|
|
31
|
+
"description": "Sets the navigation bar to translucent.",
|
|
32
|
+
"category": "style",
|
|
33
|
+
"specialCategory": null,
|
|
34
|
+
"sort": 2
|
|
35
|
+
}
|
|
27
36
|
}
|
|
28
37
|
},
|
|
29
38
|
"defaults": {
|
|
@@ -34,6 +34,7 @@ export function OnboardDot({ node }: OnboardDotComponentProps) {
|
|
|
34
34
|
selectedTheme: theme,
|
|
35
35
|
projectColors,
|
|
36
36
|
baseSize,
|
|
37
|
+
device,
|
|
37
38
|
} = useBuilderParams();
|
|
38
39
|
// OnboardDot specific attributes
|
|
39
40
|
const inactiveDotOpacity =
|
|
@@ -74,12 +75,12 @@ export function OnboardDot({ node }: OnboardDotComponentProps) {
|
|
|
74
75
|
(attrRecord.dot_thickness as string | number | undefined) ??
|
|
75
76
|
(stylesBag?.dot_thickness as string | number | undefined);
|
|
76
77
|
const dotSizeCss = useMemo((): string => {
|
|
77
|
-
const parsed = parseSize(dotThicknessRaw, baseSize);
|
|
78
|
+
const parsed = parseSize(dotThicknessRaw, baseSize, device!);
|
|
78
79
|
if (parsed === undefined) return '10px';
|
|
79
80
|
if (typeof parsed === 'number') return `${parsed}px`;
|
|
80
81
|
if (typeof parsed === 'string' && parsed.trim()) return parsed;
|
|
81
82
|
return '10px';
|
|
82
|
-
}, [dotThicknessRaw, baseSize]);
|
|
83
|
+
}, [dotThicknessRaw, baseSize, device]);
|
|
83
84
|
const dotGapCss = useMemo((): string => {
|
|
84
85
|
// Prefer px math when possible; otherwise fall back to 10px/3.
|
|
85
86
|
const px =
|
|
@@ -15,6 +15,9 @@ function StatusBarColor({ node }: StatusBarColorComponentProps) {
|
|
|
15
15
|
const setStatusBarOverrideColor = useRenderStore(
|
|
16
16
|
(s) => s.setStatusBarOverrideColor,
|
|
17
17
|
);
|
|
18
|
+
const setStatusBarOverrideTranslucent = useRenderStore(
|
|
19
|
+
(s) => s.setStatusBarOverrideTranslucent,
|
|
20
|
+
);
|
|
18
21
|
|
|
19
22
|
const rawBg = getStyleBag(node.attributes)?.backgroundColor as
|
|
20
23
|
| string
|
|
@@ -24,14 +27,25 @@ function StatusBarColor({ node }: StatusBarColorComponentProps) {
|
|
|
24
27
|
theme,
|
|
25
28
|
});
|
|
26
29
|
|
|
30
|
+
const translucent = node.attributes?.translucent;
|
|
31
|
+
|
|
27
32
|
useEffect(() => {
|
|
28
33
|
if (resolvedColor) {
|
|
29
34
|
setStatusBarOverrideColor(resolvedColor);
|
|
30
35
|
}
|
|
36
|
+
if (translucent !== undefined) {
|
|
37
|
+
setStatusBarOverrideTranslucent(translucent);
|
|
38
|
+
}
|
|
31
39
|
return () => {
|
|
32
40
|
setStatusBarOverrideColor(null);
|
|
41
|
+
setStatusBarOverrideTranslucent(null);
|
|
33
42
|
};
|
|
34
|
-
}, [
|
|
43
|
+
}, [
|
|
44
|
+
resolvedColor,
|
|
45
|
+
setStatusBarOverrideColor,
|
|
46
|
+
translucent,
|
|
47
|
+
setStatusBarOverrideTranslucent,
|
|
48
|
+
]);
|
|
35
49
|
|
|
36
50
|
return null;
|
|
37
51
|
}
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"styles": {
|
|
10
10
|
"backgroundColor": "color"
|
|
11
11
|
},
|
|
12
|
-
"
|
|
12
|
+
"translucent": "boolean"
|
|
13
13
|
}
|
|
14
14
|
},
|
|
15
15
|
"meta": {
|
|
@@ -24,6 +24,15 @@
|
|
|
24
24
|
"specialCategory": null,
|
|
25
25
|
"sort": 1
|
|
26
26
|
}
|
|
27
|
+
},
|
|
28
|
+
"attributes": {
|
|
29
|
+
"translucent": {
|
|
30
|
+
"label": "Translucent",
|
|
31
|
+
"description": "Sets the status bar to translucent.",
|
|
32
|
+
"category": "style",
|
|
33
|
+
"specialCategory": null,
|
|
34
|
+
"sort": 2
|
|
35
|
+
}
|
|
27
36
|
}
|
|
28
37
|
},
|
|
29
38
|
"defaults": {
|