@developer_tribe/react-builder 1.2.47 → 1.2.48

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.
@@ -1,4 +1,10 @@
1
- import React, { useMemo, useState, useCallback, useRef } from 'react';
1
+ import React, {
2
+ useMemo,
3
+ useState,
4
+ useCallback,
5
+ useRef,
6
+ useEffect,
7
+ } from 'react';
2
8
  import { Icon } from './Icon.generated';
3
9
  import type { IconsType } from '../types/Icons';
4
10
  import { useRenderStore } from '../store';
@@ -9,6 +15,7 @@ import {
9
15
  Modal,
10
16
  PromptManagerModal,
11
17
  } from '../modals';
18
+ import type { PromptManagerTab } from '../modals/PromptManagerModal';
12
19
  import type { Node } from '../types/Node';
13
20
  import type { Project } from '../types/Project';
14
21
  import { DebugJsonPage } from '../pages/DebugJsonPage';
@@ -46,6 +53,8 @@ export function BottomBar({
46
53
  setPreviewMode,
47
54
  setIsRtl,
48
55
  languageColumns,
56
+ quickActionConfig,
57
+ promptManagerConfig,
49
58
  } = useRenderStore((s) => ({
50
59
  localization: s.localization,
51
60
  setLocalization: s.setLocalization,
@@ -57,14 +66,23 @@ export function BottomBar({
57
66
  setPreviewMode: s.setPreviewMode,
58
67
  setIsRtl: s.setIsRtl,
59
68
  languageColumns: s.languageColumns,
69
+ quickActionConfig: s.quickActionConfig,
70
+ promptManagerConfig: s.promptManagerConfig,
60
71
  }));
72
+ const hasAiLayoutTool = Boolean(
73
+ promptManagerConfig?.generate && promptManagerConfig?.renderAiLayout,
74
+ );
61
75
 
62
76
  const [isDebugOpen, setIsDebugOpen] = useState(false);
63
77
  const [isLocalizationOpen, setIsLocalizationOpen] = useState(false);
64
78
  const [isInspectOpen, setIsInspectOpen] = useState(false);
65
- const [isPromptManagerOpen, setIsPromptManagerOpen] = useState(false);
79
+ const [promptMenuOpen, setPromptMenuOpen] = useState(false);
80
+ const [promptModalTab, setPromptModalTab] = useState<PromptManagerTab | null>(
81
+ null,
82
+ );
66
83
  const [langDropdownOpen, setLangDropdownOpen] = useState(false);
67
84
  const langTimeoutRef = useRef<ReturnType<typeof setTimeout>>();
85
+ const promptMenuTimeoutRef = useRef<ReturnType<typeof setTimeout>>();
68
86
 
69
87
  // Use languageColumns from API, fallback to localization object keys
70
88
  const availableLanguages = useMemo(() => {
@@ -99,6 +117,34 @@ export function BottomBar({
99
117
  langTimeoutRef.current = setTimeout(() => setLangDropdownOpen(false), 200);
100
118
  };
101
119
 
120
+ const handlePromptMenuMouseEnter = () => {
121
+ if (promptMenuTimeoutRef.current) {
122
+ clearTimeout(promptMenuTimeoutRef.current);
123
+ }
124
+ setPromptMenuOpen(true);
125
+ };
126
+
127
+ const handlePromptMenuMouseLeave = () => {
128
+ promptMenuTimeoutRef.current = setTimeout(
129
+ () => setPromptMenuOpen(false),
130
+ 200,
131
+ );
132
+ };
133
+
134
+ const openPromptModal = (tab: PromptManagerTab) => {
135
+ setPromptModalTab(tab);
136
+ setPromptMenuOpen(false);
137
+ };
138
+
139
+ useEffect(
140
+ () => () => {
141
+ if (langTimeoutRef.current) clearTimeout(langTimeoutRef.current);
142
+ if (promptMenuTimeoutRef.current)
143
+ clearTimeout(promptMenuTimeoutRef.current);
144
+ },
145
+ [],
146
+ );
147
+
102
148
  const handleLocalicationChange = (next: Localication) => {
103
149
  setLocalization(next);
104
150
  };
@@ -174,17 +220,86 @@ export function BottomBar({
174
220
  <Icon iconType={inspectIcon} size={20} color="currentColor" alt="" />
175
221
  </button>
176
222
 
177
- <button
178
- type="button"
179
- className={`rb-bottom-bar__button${
180
- isPromptManagerOpen ? ' is-active' : ''
181
- }`}
182
- aria-label="Prompt Manager"
183
- aria-pressed={isPromptManagerOpen}
184
- onClick={() => setIsPromptManagerOpen(true)}
223
+ <div
224
+ className="rb-bottom-bar__tool-menu-anchor"
225
+ onMouseEnter={handlePromptMenuMouseEnter}
226
+ onMouseLeave={handlePromptMenuMouseLeave}
185
227
  >
186
- <Icon iconType={promptAiIcon} size={20} color="currentColor" alt="" />
187
- </button>
228
+ <button
229
+ type="button"
230
+ className={`rb-bottom-bar__button${
231
+ promptMenuOpen || promptModalTab ? ' is-active' : ''
232
+ }`}
233
+ aria-label="Prompt Tools"
234
+ aria-pressed={promptMenuOpen || !!promptModalTab}
235
+ onClick={() => setPromptMenuOpen((prev) => !prev)}
236
+ >
237
+ <Icon
238
+ iconType={promptAiIcon}
239
+ size={20}
240
+ color="currentColor"
241
+ alt=""
242
+ />
243
+ </button>
244
+
245
+ {promptMenuOpen && (
246
+ <div className="rb-bottom-bar__tool-menu">
247
+ <div className="rb-bottom-bar__tool-menu-title">Prompt Tools</div>
248
+ {hasAiLayoutTool && (
249
+ <button
250
+ type="button"
251
+ className="rb-bottom-bar__tool-menu-item"
252
+ onClick={() => openPromptModal('ai-layout')}
253
+ >
254
+ <span className="rb-bottom-bar__tool-menu-item-label">
255
+ AI Layout Studio
256
+ </span>
257
+ <span className="rb-bottom-bar__tool-menu-item-desc">
258
+ Queue + realtime ile XML generate/edit
259
+ </span>
260
+ </button>
261
+ )}
262
+ <button
263
+ type="button"
264
+ className="rb-bottom-bar__tool-menu-item"
265
+ onClick={() => openPromptModal('xml-editor')}
266
+ >
267
+ <span className="rb-bottom-bar__tool-menu-item-label">
268
+ XML Editor
269
+ </span>
270
+ <span className="rb-bottom-bar__tool-menu-item-desc">
271
+ XML'i manuel düzenle ve uygula
272
+ </span>
273
+ </button>
274
+ <button
275
+ type="button"
276
+ className="rb-bottom-bar__tool-menu-item"
277
+ onClick={() => openPromptModal('prompt-schema')}
278
+ >
279
+ <span className="rb-bottom-bar__tool-menu-item-label">
280
+ Prompt Şeması
281
+ </span>
282
+ <span className="rb-bottom-bar__tool-menu-item-desc">
283
+ Onboard/Paywall şema referansı
284
+ </span>
285
+ </button>
286
+ </div>
287
+ )}
288
+ </div>
289
+
290
+ {quickActionConfig && (
291
+ <button
292
+ type="button"
293
+ className="rb-bottom-bar__button"
294
+ aria-label={quickActionConfig.title || quickActionConfig.label}
295
+ title={quickActionConfig.title || quickActionConfig.label}
296
+ onClick={quickActionConfig.onClick}
297
+ >
298
+ <span style={{ fontSize: 11, fontWeight: 700 }}>
299
+ {quickActionConfig.label}
300
+ </span>
301
+ </button>
302
+ )}
188
303
 
189
304
  <div className="rb-bottom-bar__spacer" />
190
305
 
@@ -323,11 +438,16 @@ export function BottomBar({
323
438
  <InspectModal onClose={() => setIsInspectOpen(false)} />
324
439
  )}
325
440
 
326
- {isPromptManagerOpen && (
441
+ {promptModalTab && (
327
442
  <PromptManagerModal
328
443
  data={data}
329
444
  setData={setData}
330
- onClose={() => setIsPromptManagerOpen(false)}
445
+ initialTab={promptModalTab}
446
+ showTabs={promptModalTab !== 'ai-layout'}
447
+ title={
448
+ promptModalTab === 'ai-layout' ? 'AI Layout Studio' : undefined
449
+ }
450
+ onClose={() => setPromptModalTab(null)}
331
451
  />
332
452
  )}
333
453
  </>
@@ -14,6 +14,40 @@ type DeviceButtonProps = {
14
14
  isFavorite?: boolean;
15
15
  onToggleFavorite?: (device: Device, e: React.MouseEvent) => void;
16
16
  onEdit?: (device: Device, e: React.MouseEvent) => void;
17
+ sizeVariant?: 'header' | 'modal';
18
+ };
19
+
20
+ const SHORT_NAME_PREFIXES = [
21
+ 'samsung galaxy',
22
+ 'google pixel',
23
+ 'motorola',
24
+ 'oneplus',
25
+ 'huawei',
26
+ 'xiaomi',
27
+ 'redmi',
28
+ 'galaxy',
29
+ 'iphone',
30
+ 'ipad',
31
+ 'pixel',
32
+ 'sony',
33
+ 'nokia',
34
+ 'oppo',
35
+ 'vivo',
36
+ ];
37
+
38
+ const clamp = (value: number, min: number, max: number): number =>
39
+ Math.min(max, Math.max(min, value));
40
+
41
+ const getCompactDeviceName = (name: string): string => {
42
+ const trimmed = name.trim();
43
+ const lower = trimmed.toLowerCase();
44
+ const matchedPrefix = SHORT_NAME_PREFIXES.find((prefix) =>
45
+ lower.startsWith(`${prefix} `),
46
+ );
47
+
48
+ if (!matchedPrefix) return trimmed;
49
+ const compact = trimmed.slice(matchedPrefix.length).trim();
50
+ return compact || trimmed;
17
51
  };
18
52
 
19
53
  export function DeviceButton({
@@ -23,24 +57,37 @@ export function DeviceButton({
23
57
  isFavorite,
24
58
  onToggleFavorite,
25
59
  onEdit,
60
+ sizeVariant = 'header',
26
61
  }: DeviceButtonProps) {
27
62
  const platformIcon = platformIcons[device.platform];
28
- const aspect =
29
- device.aspect ??
30
- (() => {
31
- const r = device.height / device.width;
32
- if (r >= 2.05) return 'tall' as const;
33
- if (r <= 1.75) return 'wide' as const;
34
- return 'regular' as const;
35
- })();
63
+ const safeWidth = Math.max(1, Number(device.width) || 1);
64
+ const safeHeight = Math.max(1, Number(device.height) || 1);
65
+ const compactName = getCompactDeviceName(device.name);
66
+ const tooltipText = `${device.name} - ${safeWidth}x${safeHeight}`;
67
+ const longSide = Math.max(safeWidth, safeHeight);
68
+ const baseLongSide = sizeVariant === 'modal' ? 2600 : 2400;
69
+ const basePreviewHeight = sizeVariant === 'modal' ? 58 : 30;
70
+ const typeBoost = device.type === 'tablet' ? 1.12 : 1;
71
+ const previewScale = clamp(
72
+ Math.sqrt(longSide / baseLongSide) * typeBoost,
73
+ sizeVariant === 'modal' ? 0.82 : 0.78,
74
+ sizeVariant === 'modal' ? 1.25 : 1.18,
75
+ );
76
+ const previewHeight = Math.round(basePreviewHeight * previewScale);
77
+ const frameStyle = {
78
+ aspectRatio: `${safeWidth} / ${safeHeight}`,
79
+ '--device-preview-height': `${previewHeight}px`,
80
+ } as React.CSSProperties;
36
81
 
37
82
  return (
38
83
  <button
39
84
  type="button"
40
85
  className={`editor-device-button${
41
86
  selectedDevice === device ? ' editor-device-button--selected' : ''
42
- }`}
87
+ } editor-device-button--${sizeVariant}`}
43
88
  onClick={() => onSelect(device)}
89
+ title={tooltipText}
90
+ aria-label={tooltipText}
44
91
  >
45
92
  {onToggleFavorite && (
46
93
  <span
@@ -51,10 +98,6 @@ export function DeviceButton({
51
98
  }}
52
99
  title={isFavorite ? 'Remove from favorites' : 'Add to favorites'}
53
100
  style={{
54
- position: 'absolute',
55
- top: 4,
56
- right: 4,
57
- cursor: 'pointer',
58
101
  opacity: isFavorite ? 1 : 0.3,
59
102
  color: isFavorite ? '#ecc538' : 'currentColor',
60
103
  }}
@@ -73,8 +116,30 @@ export function DeviceButton({
73
116
  </svg>
74
117
  </span>
75
118
  )}
76
- {device.name} <br />
77
- {device.width}×{device.height} ({aspect})
119
+
120
+ <span className="editor-device-button__popover" aria-hidden="true">
121
+ <span className="editor-device-button__popover-title">
122
+ {device.name}
123
+ </span>
124
+ <span className="editor-device-button__popover-meta">
125
+ {safeWidth}x{safeHeight}
126
+ </span>
127
+ </span>
128
+
129
+ <span className="editor-device-button__preview" aria-hidden="true">
130
+ <span
131
+ className="editor-device-button__frame"
132
+ data-platform={device.platform}
133
+ data-type={device.type}
134
+ style={frameStyle}
135
+ >
136
+ <span className="editor-device-button__screen" />
137
+ <span className="editor-device-button__notch" />
138
+ </span>
139
+ </span>
140
+
141
+ <span className="editor-device-button__name">{compactName}</span>
142
+
78
143
  {platformIcon && (
79
144
  <span className="editor-device-button__platform">
80
145
  <img src={platformIcon} alt="" aria-hidden="true" />
@@ -88,13 +153,7 @@ export function DeviceButton({
88
153
  onEdit(device, e);
89
154
  }}
90
155
  title="Edit device"
91
- style={{
92
- position: 'absolute',
93
- bottom: 4,
94
- left: 4,
95
- cursor: 'pointer',
96
- opacity: 0.5,
97
- }}
156
+ style={{ opacity: 0.5 }}
98
157
  >
99
158
  <svg
100
159
  width="12"
@@ -288,14 +288,15 @@ export function EditorHeader({
288
288
  device={deviceOption}
289
289
  isFavorite={favoriteDevices.includes(deviceOption.name)}
290
290
  onToggleFavorite={(d) => toggleFavoriteDevice(d.name)}
291
+ sizeVariant="header"
291
292
  />
292
293
  ))}
293
294
  <button
294
- className="editor-device-button"
295
+ className="editor-device-button editor-device-button--header editor-device-button--more"
295
296
  aria-label="More devices"
296
297
  onClick={() => setIsDevicesModalOpen(true)}
297
298
  >
298
- More devices
299
+ <span className="editor-device-button__more-label">More</span>
299
300
  </button>
300
301
  </div>
301
302
  <div className="editor-header__actions">
package/src/index.web.ts CHANGED
@@ -14,11 +14,26 @@ export type { EventObjectGenerated } from './build-components/OnboardButton/Onbo
14
14
 
15
15
  // Host-app API: store (tema vb. builder içinde kullanılır; örn. tema toggle'ı builder'a senkronize etmek için).
16
16
  export { useRenderStore } from './store';
17
+ export type {
18
+ BuilderPromptManagerAiLayoutProps,
19
+ BuilderPromptManagerConfig,
20
+ BuilderPromptManagerGenerateRequest,
21
+ BuilderPromptManagerGenerateResponse,
22
+ BuilderPromptManagerMode,
23
+ BuilderPromptManagerModel,
24
+ BuilderPromptSchemaType,
25
+ } from './store';
17
26
 
18
27
  // Host-app API: let parent apps inject a custom "Text" field renderer.
19
28
  import { useRenderStore } from './store';
20
29
  import type { ComponentType } from 'react';
21
- import type { LocalizationApiConfig, LanguageColumn } from './store';
30
+ import type {
31
+ BuilderMediaFieldProps,
32
+ BuilderPromptManagerConfig,
33
+ LocalizationApiConfig,
34
+ LanguageColumn,
35
+ QuickActionConfig,
36
+ } from './store';
22
37
 
23
38
  export function setBuilderStringChildrenField(
24
39
  comp?: ComponentType<{ value: string; onChange: (v: string) => void }>,
@@ -33,3 +48,19 @@ export function setBuilderLocalizationConfig(config?: LocalizationApiConfig) {
33
48
  export function setBuilderLanguageColumns(cols: LanguageColumn[]) {
34
49
  useRenderStore.getState().setLanguageColumns(cols);
35
50
  }
51
+
52
+ export function setBuilderQuickAction(config?: QuickActionConfig) {
53
+ useRenderStore.getState().setQuickActionConfig(config);
54
+ }
55
+
56
+ export function setBuilderMediaField(
57
+ comp?: ComponentType<BuilderMediaFieldProps>,
58
+ ) {
59
+ useRenderStore.getState().setRenderMediaField(comp);
60
+ }
61
+
62
+ export function setBuilderPromptManagerConfig(
63
+ config?: BuilderPromptManagerConfig,
64
+ ) {
65
+ useRenderStore.getState().setPromptManagerConfig(config);
66
+ }
@@ -116,6 +116,7 @@ export function DeviceSelectorModal({
116
116
  isFavorite={true}
117
117
  onToggleFavorite={(d) => toggleFavoriteDevice(d.name)}
118
118
  onEdit={(d) => setEditingDevice(d)}
119
+ sizeVariant="modal"
119
120
  />
120
121
  ))}
121
122
  </div>
@@ -147,6 +148,7 @@ export function DeviceSelectorModal({
147
148
  ? (d) => setEditingDevice(d)
148
149
  : undefined
149
150
  }
151
+ sizeVariant="modal"
150
152
  />
151
153
  ))}
152
154
  </div>