@developer_tribe/react-builder 1.0.1 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (218) hide show
  1. package/dist/AttributesEditor.d.ts +3 -1
  2. package/dist/DeviceMockFrame.d.ts +2 -1
  3. package/dist/RenderPage.d.ts +5 -3
  4. package/dist/attributes-editor/Field.d.ts +17 -0
  5. package/dist/attributes-editor/FieldInfoTooltip.d.ts +7 -0
  6. package/dist/attributes-editor/LayoutPreviewPicker.d.ts +12 -0
  7. package/dist/attributes-editor/SpecialCategorySection.d.ts +20 -0
  8. package/dist/attributes-editor/types.d.ts +14 -0
  9. package/dist/background.jpg +0 -0
  10. package/dist/build-components/BackgroundImage/BackgroundImage.d.ts +5 -0
  11. package/dist/build-components/BackgroundImage/BackgroundImageProps.generated.d.ts +44 -0
  12. package/dist/build-components/Button/Button.d.ts +1 -1
  13. package/dist/build-components/Button/ButtonProps.generated.d.ts +33 -1
  14. package/dist/build-components/Carousel/CarouselProps.generated.d.ts +34 -1
  15. package/dist/build-components/CarouselButtons/CarouselButtonsProps.generated.d.ts +32 -0
  16. package/dist/build-components/CarouselDots/CarouselDotsProps.generated.d.ts +32 -0
  17. package/dist/build-components/CarouselItem/CarouselItemProps.generated.d.ts +34 -1
  18. package/dist/build-components/CarouselProvider/CarouselProviderProps.generated.d.ts +34 -1
  19. package/dist/build-components/Image/ImageProps.generated.d.ts +32 -3
  20. package/dist/build-components/Onboard/OnboardProps.generated.d.ts +34 -1
  21. package/dist/build-components/OnboardButton/OnboardButtonProps.generated.d.ts +32 -0
  22. package/dist/build-components/OnboardButtons/OnboardButtonsProps.generated.d.ts +32 -0
  23. package/dist/build-components/OnboardDot/OnboardDot.d.ts +1 -1
  24. package/dist/build-components/OnboardDot/OnboardDotProps.generated.d.ts +29 -0
  25. package/dist/build-components/OnboardFooter/OnboardFooterProps.generated.d.ts +11 -5
  26. package/dist/build-components/OnboardImage/OnboardImageProps.generated.d.ts +32 -3
  27. package/dist/build-components/OnboardItem/OnboardItemProps.generated.d.ts +31 -3
  28. package/dist/build-components/OnboardProvider/OnboardProviderProps.generated.d.ts +32 -5
  29. package/dist/build-components/OnboardSubtitle/OnboardSubtitleProps.generated.d.ts +11 -5
  30. package/dist/build-components/OnboardTitle/OnboardTitleProps.generated.d.ts +11 -5
  31. package/dist/build-components/Text/TextProps.generated.d.ts +11 -5
  32. package/dist/build-components/View/ViewProps.generated.d.ts +10 -4
  33. package/dist/build-components/index.d.ts +2 -1
  34. package/dist/build-components/patterns.generated.d.ts +6288 -136
  35. package/dist/components/AttributesEditorPanel.d.ts +3 -4
  36. package/dist/components/Breadcrumb.d.ts +3 -1
  37. package/dist/components/Builder.d.ts +2 -1
  38. package/dist/components/BuilderButton.d.ts +9 -0
  39. package/dist/components/Checkbox.d.ts +17 -0
  40. package/dist/components/DeviceButton.d.ts +8 -0
  41. package/dist/components/DeviceNavigationBar.d.ts +10 -0
  42. package/dist/components/DeviceStatusBar.d.ts +9 -0
  43. package/dist/components/EditorHeader.d.ts +3 -8
  44. package/dist/index.cjs.js +5 -5
  45. package/dist/index.cjs.js.map +1 -1
  46. package/dist/index.d.ts +2 -2
  47. package/dist/index.esm.js +5 -5
  48. package/dist/index.esm.js.map +1 -1
  49. package/dist/mockOS/components/MockLaunchScreenComponent.d.ts +6 -0
  50. package/dist/mockOS/components/MockOSRouter.d.ts +8 -0
  51. package/dist/mockOS/components/PermissionModal.d.ts +9 -0
  52. package/dist/mockOS/context/MockOSContext.d.ts +36 -0
  53. package/dist/mockOS/hooks/useMockNavigation.d.ts +3 -0
  54. package/dist/mockOS/hooks/useMockPermission.d.ts +3 -0
  55. package/dist/mockOS/index.d.ts +9 -0
  56. package/dist/mockOS/managers/mockPermissionManager.d.ts +10 -0
  57. package/dist/mockOS/managers/navigationManager.d.ts +17 -0
  58. package/dist/modals/AddComponentModal.d.ts +8 -0
  59. package/dist/modals/ColorModal.d.ts +11 -0
  60. package/dist/modals/DeviceSelectorModal.d.ts +9 -0
  61. package/dist/modals/LocalicationModal.d.ts +8 -0
  62. package/dist/modals/Modal.d.ts +12 -0
  63. package/dist/modals/index.d.ts +5 -0
  64. package/dist/pages/ProjectPage.d.ts +3 -3
  65. package/dist/pages/tabs/BuilderPanel.d.ts +8 -0
  66. package/dist/pages/tabs/{DebugTab.d.ts → SideTool.d.ts} +2 -2
  67. package/dist/store.d.ts +7 -3
  68. package/dist/styles.css +1 -1
  69. package/dist/types/Project.d.ts +11 -0
  70. package/dist/utils/analyseNode.d.ts +1 -0
  71. package/dist/utils/extractTextStyle.d.ts +8 -1
  72. package/dist/utils/extractViewStyle.d.ts +8 -1
  73. package/dist/utils/parseColor.d.ts +7 -0
  74. package/dist/utils/patterns.d.ts +24 -0
  75. package/package.json +2 -1
  76. package/scripts/prebuild/utils/createGeneratedProps.js +11 -3
  77. package/scripts/prebuild/utils/validateAllComponentsOrThrow.js +45 -6
  78. package/scripts/prebuild/utils/validatePatternJson.js +13 -5
  79. package/src/AttributesEditor.tsx +493 -310
  80. package/src/DeviceMockFrame.tsx +21 -37
  81. package/src/RenderPage.tsx +86 -7
  82. package/src/assets/images/android.svg +42 -42
  83. package/src/assets/images/apple.svg +15 -15
  84. package/src/attributes-editor/Field.tsx +669 -0
  85. package/src/attributes-editor/FieldInfoTooltip.tsx +49 -0
  86. package/src/attributes-editor/LayoutPreviewPicker.tsx +199 -0
  87. package/src/attributes-editor/SpecialCategorySection.tsx +285 -0
  88. package/src/attributes-editor/types.ts +30 -0
  89. package/src/build-components/BackgroundImage/BackgroundImage.tsx +87 -0
  90. package/src/build-components/BackgroundImage/BackgroundImageProps.generated.ts +60 -0
  91. package/src/build-components/BackgroundImage/pattern.json +45 -0
  92. package/src/build-components/Button/Button.tsx +37 -2
  93. package/src/build-components/Button/ButtonProps.generated.ts +44 -1
  94. package/src/build-components/Button/pattern.json +31 -2
  95. package/src/build-components/Carousel/Carousel.tsx +39 -2
  96. package/src/build-components/Carousel/CarouselProps.generated.ts +46 -1
  97. package/src/build-components/Carousel/pattern.json +10 -0
  98. package/src/build-components/CarouselButtons/CarouselButtons.tsx +21 -2
  99. package/src/build-components/CarouselButtons/CarouselButtonsProps.generated.ts +43 -0
  100. package/src/build-components/CarouselButtons/pattern.json +22 -0
  101. package/src/build-components/CarouselDots/CarouselDots.tsx +49 -8
  102. package/src/build-components/CarouselDots/CarouselDotsProps.generated.ts +43 -0
  103. package/src/build-components/CarouselDots/pattern.json +15 -0
  104. package/src/build-components/CarouselItem/CarouselItem.tsx +21 -2
  105. package/src/build-components/CarouselItem/CarouselItemProps.generated.ts +46 -1
  106. package/src/build-components/CarouselItem/pattern.json +7 -0
  107. package/src/build-components/CarouselProvider/CarouselProvider.tsx +21 -2
  108. package/src/build-components/CarouselProvider/CarouselProviderProps.generated.ts +46 -1
  109. package/src/build-components/CarouselProvider/pattern.json +7 -0
  110. package/src/build-components/Image/Image.tsx +33 -2
  111. package/src/build-components/Image/ImageProps.generated.ts +43 -3
  112. package/src/build-components/Image/pattern.json +46 -3
  113. package/src/build-components/Onboard/Onboard.tsx +6 -1
  114. package/src/build-components/Onboard/OnboardProps.generated.ts +46 -1
  115. package/src/build-components/Onboard/pattern.json +11 -0
  116. package/src/build-components/OnboardButton/OnboardButton.tsx +54 -6
  117. package/src/build-components/OnboardButton/OnboardButtonProps.generated.ts +43 -0
  118. package/src/build-components/OnboardButton/pattern.json +71 -5
  119. package/src/build-components/OnboardButtons/OnboardButtons.tsx +33 -11
  120. package/src/build-components/OnboardButtons/OnboardButtonsProps.generated.ts +43 -0
  121. package/src/build-components/OnboardButtons/pattern.json +70 -4
  122. package/src/build-components/OnboardDot/OnboardDot.tsx +113 -4
  123. package/src/build-components/OnboardDot/OnboardDotProps.generated.ts +29 -0
  124. package/src/build-components/OnboardDot/pattern.json +55 -2
  125. package/src/build-components/OnboardFooter/OnboardFooter.tsx +20 -4
  126. package/src/build-components/OnboardFooter/OnboardFooterProps.generated.ts +11 -5
  127. package/src/build-components/OnboardFooter/pattern.json +58 -2
  128. package/src/build-components/OnboardImage/OnboardImage.tsx +49 -5
  129. package/src/build-components/OnboardImage/OnboardImageProps.generated.ts +43 -3
  130. package/src/build-components/OnboardImage/pattern.json +21 -0
  131. package/src/build-components/OnboardItem/OnboardItem.tsx +17 -1
  132. package/src/build-components/OnboardItem/OnboardItemProps.generated.ts +42 -3
  133. package/src/build-components/OnboardItem/pattern.json +38 -2
  134. package/src/build-components/OnboardProvider/OnboardProvider.tsx +52 -18
  135. package/src/build-components/OnboardProvider/OnboardProviderProps.generated.ts +44 -5
  136. package/src/build-components/OnboardProvider/pattern.json +44 -5
  137. package/src/build-components/OnboardSubtitle/OnboardSubtitleProps.generated.ts +11 -5
  138. package/src/build-components/OnboardSubtitle/pattern.json +7 -1
  139. package/src/build-components/OnboardTitle/OnboardTitleProps.generated.ts +11 -5
  140. package/src/build-components/OnboardTitle/pattern.json +7 -1
  141. package/src/build-components/RenderNode.generated.tsx +3 -0
  142. package/src/build-components/Text/Text.tsx +34 -6
  143. package/src/build-components/Text/TextProps.generated.ts +11 -5
  144. package/src/build-components/Text/pattern.json +38 -2
  145. package/src/build-components/View/View.tsx +33 -6
  146. package/src/build-components/View/ViewProps.generated.ts +10 -4
  147. package/src/build-components/View/pattern.json +285 -19
  148. package/src/build-components/index.ts +5 -0
  149. package/src/build-components/patterns.generated.ts +6346 -143
  150. package/src/components/AttributesEditorPanel.tsx +17 -64
  151. package/src/components/Breadcrumb.tsx +37 -5
  152. package/src/components/Builder.tsx +311 -108
  153. package/src/components/BuilderButton.tsx +127 -0
  154. package/src/components/Checkbox.tsx +81 -0
  155. package/src/components/DeviceButton.tsx +39 -0
  156. package/src/components/DeviceNavigationBar.tsx +201 -0
  157. package/src/components/DeviceStatusBar.tsx +85 -0
  158. package/src/components/EditorHeader.tsx +26 -74
  159. package/src/index.ts +2 -2
  160. package/src/mockOS/components/MockLaunchScreenComponent.tsx +43 -0
  161. package/src/mockOS/components/MockOSRouter.tsx +123 -0
  162. package/src/mockOS/components/PermissionModal.tsx +270 -0
  163. package/src/mockOS/context/MockOSContext.tsx +179 -0
  164. package/src/mockOS/hooks/useMockNavigation.ts +11 -0
  165. package/src/mockOS/hooks/useMockPermission.ts +11 -0
  166. package/src/mockOS/index.ts +26 -0
  167. package/src/mockOS/managers/mockPermissionManager.ts +54 -0
  168. package/src/mockOS/managers/navigationManager.ts +91 -0
  169. package/src/modals/AddComponentModal.tsx +313 -0
  170. package/src/modals/ColorModal.tsx +425 -0
  171. package/src/modals/DeviceSelectorModal.tsx +57 -0
  172. package/src/modals/LocalicationModal.tsx +54 -0
  173. package/src/modals/Modal.tsx +57 -0
  174. package/src/modals/index.ts +5 -0
  175. package/src/pages/ProjectPage.tsx +307 -71
  176. package/src/pages/tabs/{BuilderTab.tsx → BuilderPanel.tsx} +13 -9
  177. package/src/pages/tabs/SideTool.tsx +259 -0
  178. package/src/size-matters/index.ts +27 -5
  179. package/src/store.ts +13 -5
  180. package/src/styles/base/_global.scss +404 -0
  181. package/src/styles/components/_attributes-editor.scss +273 -0
  182. package/src/styles/components/_editor-shell.scss +212 -0
  183. package/src/styles/components/_mockos-router.scss +140 -0
  184. package/src/styles/components/_ui-components.scss +183 -0
  185. package/src/styles/foundation/_colors.scss +8 -0
  186. package/src/styles/{_mixins.scss → foundation/_mixins.scss} +5 -4
  187. package/src/styles/{_reset.scss → foundation/_reset.scss} +5 -2
  188. package/src/styles/foundation/_sizes.scss +37 -0
  189. package/src/styles/foundation/_typography.scss +4 -0
  190. package/src/styles/foundation/_variables.scss +3 -0
  191. package/src/styles/index.scss +22 -136
  192. package/src/styles/layout/_builder.scss +124 -0
  193. package/src/styles/layout/_pages.scss +3 -0
  194. package/src/styles/modals/_add-component.scss +122 -0
  195. package/src/styles/modals/_color-modal.scss +159 -0
  196. package/src/styles/modals/_device-selector.scss +18 -0
  197. package/src/styles/modals/_localication-modal.scss +68 -0
  198. package/src/styles/modals/_modal-shell.scss +46 -0
  199. package/src/styles/utilities/_carousel.scss +125 -0
  200. package/src/types/Project.ts +14 -0
  201. package/src/types/images.d.ts +8 -0
  202. package/src/utils/analyseNode.ts +98 -0
  203. package/src/utils/extractTextStyle.ts +28 -10
  204. package/src/utils/extractViewStyle.ts +77 -9
  205. package/src/utils/parseColor.ts +43 -0
  206. package/src/utils/patterns.ts +33 -0
  207. package/dist/build-components/OnboardDot/OnboardExpandingDotProps.generated.d.ts +0 -10
  208. package/dist/pages/tabs/BuilderTab.d.ts +0 -9
  209. package/dist/pages/tabs/PreviewTab.d.ts +0 -3
  210. package/src/build-components/OnboardDot/OnboardExpandingDotProps.generated.ts +0 -20
  211. package/src/pages/tabs/DebugTab.tsx +0 -23
  212. package/src/pages/tabs/PreviewTab.tsx +0 -194
  213. package/src/styles/_variables.scss +0 -27
  214. package/src/styles/builder.scss +0 -60
  215. package/src/styles/components.scss +0 -88
  216. package/src/styles/editor.scss +0 -174
  217. package/src/styles/global.scss +0 -200
  218. package/src/styles/pages.scss +0 -2
@@ -0,0 +1,127 @@
1
+ import { useRef } from 'react';
2
+ import {
3
+ isNodeNullOrUndefined,
4
+ isNodeString,
5
+ Node,
6
+ NodeData,
7
+ NodeDefaultAttribute,
8
+ } from '..';
9
+ import { getPatternByType } from '../utils/patterns';
10
+
11
+ export type BuilderButtonProps = {
12
+ node: Node;
13
+ onClick: () => void;
14
+ onDelete?: (node: Node) => void;
15
+ onMoveUp?: () => void;
16
+ onMoveDown?: () => void;
17
+ };
18
+
19
+ export function BuilderButton({
20
+ node,
21
+ onClick,
22
+ onDelete,
23
+ onMoveUp,
24
+ onMoveDown,
25
+ }: BuilderButtonProps) {
26
+ if (isNodeNullOrUndefined(node)) {
27
+ return <div className="builder__placeholder">Null or undefined</div>;
28
+ }
29
+ if (isNodeString(node)) {
30
+ return <div className="builder__text">{node as string}</div>;
31
+ }
32
+ const nodeData = node as NodeData<NodeDefaultAttribute>;
33
+
34
+ const longPressTimeoutRef = useRef<number | null>(null);
35
+ const longPressTriggeredRef = useRef(false);
36
+
37
+ const handleDelete = () => {
38
+ if (onDelete) {
39
+ onDelete(node);
40
+ }
41
+ };
42
+
43
+ const clearLongPress = () => {
44
+ if (longPressTimeoutRef.current !== null) {
45
+ window.clearTimeout(longPressTimeoutRef.current);
46
+ longPressTimeoutRef.current = null;
47
+ }
48
+ };
49
+
50
+ const handlePressStart = () => {
51
+ longPressTriggeredRef.current = false;
52
+ longPressTimeoutRef.current = window.setTimeout(() => {
53
+ longPressTriggeredRef.current = true;
54
+ const shouldDelete = window.confirm('Do you want to delete');
55
+ if (shouldDelete) {
56
+ handleDelete();
57
+ }
58
+ }, 600);
59
+ };
60
+
61
+ const handlePressEnd = () => {
62
+ if (!longPressTriggeredRef.current) {
63
+ onClick();
64
+ }
65
+ clearLongPress();
66
+ };
67
+
68
+ const handlePressCancel = () => {
69
+ clearLongPress();
70
+ };
71
+
72
+ let extra = '';
73
+ if (nodeData.attributes?.condition) {
74
+ extra = ` (${nodeData.attributes.condition} ${nodeData.attributes.conditionVariable})`;
75
+ }
76
+ const patternLabel = getPatternByType(nodeData.type)?.meta?.label?.trim();
77
+ const baseLabel =
78
+ patternLabel && patternLabel.length > 0 ? patternLabel : nodeData.type;
79
+ const conditionLabel = extra.trim() ? extra : '';
80
+
81
+ return (
82
+ <div className="builder__button">
83
+ {(onMoveUp || onMoveDown) && (
84
+ <div className="builder__sort-controls">
85
+ <button
86
+ type="button"
87
+ className="builder__sort-button"
88
+ onClick={(event) => {
89
+ event.stopPropagation();
90
+ onMoveUp?.();
91
+ }}
92
+ disabled={!onMoveUp}
93
+ aria-label="Move up"
94
+ >
95
+
96
+ </button>
97
+ <button
98
+ type="button"
99
+ className="builder__sort-button"
100
+ onClick={(event) => {
101
+ event.stopPropagation();
102
+ onMoveDown?.();
103
+ }}
104
+ disabled={!onMoveDown}
105
+ aria-label="Move down"
106
+ >
107
+
108
+ </button>
109
+ </div>
110
+ )}
111
+ <a
112
+ className="builder__button-link"
113
+ onMouseDown={handlePressStart}
114
+ onMouseUp={handlePressEnd}
115
+ onMouseLeave={handlePressCancel}
116
+ onTouchStart={handlePressStart}
117
+ onTouchEnd={handlePressEnd}
118
+ onTouchCancel={handlePressCancel}
119
+ >
120
+ {baseLabel}
121
+ </a>
122
+ {conditionLabel && (
123
+ <span className="builder__button-condition">{conditionLabel}</span>
124
+ )}
125
+ </div>
126
+ );
127
+ }
@@ -0,0 +1,81 @@
1
+ import React, { useId } from 'react';
2
+
3
+ type CheckboxChangeHandler = (
4
+ checked: boolean,
5
+ event: React.ChangeEvent<HTMLInputElement>,
6
+ ) => void;
7
+
8
+ export type CheckboxProps = Omit<
9
+ React.InputHTMLAttributes<HTMLInputElement>,
10
+ 'type' | 'onChange' | 'className'
11
+ > & {
12
+ label?: React.ReactNode;
13
+ helperText?: React.ReactNode;
14
+ className?: string;
15
+ inputClassName?: string;
16
+ onChange?: CheckboxChangeHandler;
17
+ };
18
+
19
+ export const Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>(
20
+ (
21
+ {
22
+ label,
23
+ helperText,
24
+ className,
25
+ inputClassName,
26
+ onChange,
27
+ id,
28
+ disabled,
29
+ ...rest
30
+ },
31
+ ref,
32
+ ) => {
33
+ const autoId = useId();
34
+ const inputId = id ?? autoId;
35
+ const helperId = helperText ? `${inputId}-helper` : undefined;
36
+
37
+ const wrapperClassName = [
38
+ 'builder-checkbox',
39
+ disabled ? 'builder-checkbox--disabled' : undefined,
40
+ className,
41
+ ]
42
+ .filter(Boolean)
43
+ .join(' ');
44
+
45
+ const nativeClassName = ['builder-checkbox__native', inputClassName]
46
+ .filter(Boolean)
47
+ .join(' ');
48
+
49
+ const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
50
+ onChange?.(event.target.checked, event);
51
+ };
52
+
53
+ return (
54
+ <div className={wrapperClassName}>
55
+ <label htmlFor={inputId} className="builder-checkbox__label">
56
+ <input
57
+ {...rest}
58
+ ref={ref}
59
+ id={inputId}
60
+ type="checkbox"
61
+ className={nativeClassName}
62
+ onChange={handleChange}
63
+ disabled={disabled}
64
+ aria-describedby={helperId}
65
+ />
66
+ <span className="builder-checkbox__control" aria-hidden="true" />
67
+ {label ? (
68
+ <span className="builder-checkbox__text">{label}</span>
69
+ ) : null}
70
+ </label>
71
+ {helperText ? (
72
+ <span id={helperId} className="builder-checkbox__helper">
73
+ {helperText}
74
+ </span>
75
+ ) : null}
76
+ </div>
77
+ );
78
+ },
79
+ );
80
+
81
+ Checkbox.displayName = 'Checkbox';
@@ -0,0 +1,39 @@
1
+ import React from 'react';
2
+ import { Device } from '../types/Device';
3
+ import androidIcon from '../assets/images/android.svg';
4
+ import iosIcon from '../assets/images/apple.svg';
5
+
6
+ const platformIcons: Record<string, string> = {
7
+ android: androidIcon,
8
+ ios: iosIcon,
9
+ };
10
+
11
+ type DeviceButtonProps = {
12
+ device: Device;
13
+ selectedDevice: Device | null;
14
+ onSelect: (device: Device) => void;
15
+ };
16
+
17
+ export function DeviceButton({
18
+ device,
19
+ selectedDevice,
20
+ onSelect,
21
+ }: DeviceButtonProps) {
22
+ const platformIcon = platformIcons[device.platform];
23
+
24
+ return (
25
+ <button
26
+ type="button"
27
+ className={`editor-device-button${
28
+ selectedDevice === device ? ' editor-device-button--selected' : ''
29
+ }`}
30
+ onClick={() => onSelect(device)}
31
+ >
32
+ {device.name} <br />
33
+ {device.width}x{device.height}
34
+ {platformIcon && <img src={platformIcon} alt="" aria-hidden="true" />}
35
+ </button>
36
+ );
37
+ }
38
+
39
+ export default DeviceButton;
@@ -0,0 +1,201 @@
1
+ import React from 'react';
2
+ import { Device } from '../types/Device';
3
+ import { useMockOSContext } from '../mockOS';
4
+
5
+ type DeviceNavigationBarProps = {
6
+ height: number;
7
+ backgroundColor: string;
8
+ platform?: Device['platform'];
9
+ navigationBarType?: Device['navigationBarType'];
10
+ isDark: boolean;
11
+ };
12
+
13
+ const basePillStyle: React.CSSProperties = {
14
+ height: 5,
15
+ borderRadius: 999,
16
+ width: 120,
17
+ };
18
+
19
+ const tabItems = ['Today', 'Search', 'Profile'];
20
+
21
+ export function DeviceNavigationBar({
22
+ height,
23
+ backgroundColor,
24
+ platform = 'android',
25
+ navigationBarType = 'gesture',
26
+ isDark,
27
+ }: DeviceNavigationBarProps) {
28
+ const context = useMockOSContext();
29
+
30
+ if (!height) {
31
+ return null;
32
+ }
33
+
34
+ const barColor = isDark ? 'rgba(255, 255, 255, 0.85)' : 'rgba(0, 0, 0, 0.7)';
35
+ const mutedColor = isDark
36
+ ? 'rgba(255, 255, 255, 0.55)'
37
+ : 'rgba(0, 0, 0, 0.4)';
38
+
39
+ function handleBackButton() {
40
+ console.log('handleBackButton', context);
41
+ if (context) {
42
+ const canGoBack = context.goBack();
43
+ // If can't go back, go to launchscreen
44
+ if (!canGoBack) {
45
+ context.navigation('launchscreen');
46
+ }
47
+ } else {
48
+ alert('Navigation: Back\n(Mock OS context not available)');
49
+ }
50
+ }
51
+
52
+ function handleHomeButton() {
53
+ if (context) {
54
+ context.navigation('launchscreen');
55
+ } else {
56
+ alert('Navigation: Home\n(Mock OS context not available)');
57
+ }
58
+ }
59
+
60
+ const containerStyle: React.CSSProperties = {
61
+ height,
62
+ backgroundColor,
63
+ display: 'flex',
64
+ alignItems: 'center',
65
+ justifyContent: 'center',
66
+ padding: '0 16px',
67
+ };
68
+
69
+ if (platform === 'ios') {
70
+ switch (navigationBarType) {
71
+ case 'homeIndicator':
72
+ return (
73
+ <div className="device-navigation-bar" style={containerStyle}>
74
+ <div
75
+ style={{
76
+ ...basePillStyle,
77
+ backgroundColor: barColor,
78
+ }}
79
+ />
80
+ </div>
81
+ );
82
+ case 'tabBar':
83
+ return (
84
+ <div className="device-navigation-bar" style={containerStyle}>
85
+ <div
86
+ style={{
87
+ display: 'flex',
88
+ width: '100%',
89
+ maxWidth: 280,
90
+ justifyContent: 'space-between',
91
+ fontSize: 11,
92
+ textTransform: 'uppercase',
93
+ letterSpacing: 0.4,
94
+ gap: 20,
95
+ }}
96
+ >
97
+ {tabItems.map((item) => (
98
+ <div
99
+ key={item}
100
+ style={{
101
+ display: 'flex',
102
+ flexDirection: 'column',
103
+ alignItems: 'center',
104
+ gap: 4,
105
+ color: item === 'Today' ? barColor : mutedColor,
106
+ }}
107
+ >
108
+ <div
109
+ style={{
110
+ width: 20,
111
+ height: 20,
112
+ borderRadius: 6,
113
+ backgroundColor: item === 'Today' ? barColor : mutedColor,
114
+ opacity: 0.9,
115
+ }}
116
+ />
117
+ <span>{item}</span>
118
+ </div>
119
+ ))}
120
+ </div>
121
+ </div>
122
+ );
123
+ default:
124
+ return null;
125
+ }
126
+ }
127
+
128
+ // Android styles
129
+ if (navigationBarType === 'threeButtons') {
130
+ const buttonStyle: React.CSSProperties = {
131
+ position: 'relative',
132
+ width: 40,
133
+ height: 40,
134
+ display: 'flex',
135
+ alignItems: 'center',
136
+ justifyContent: 'center',
137
+ fontSize: 18,
138
+ color: barColor,
139
+ cursor: 'pointer',
140
+ userSelect: 'none',
141
+ };
142
+
143
+ return (
144
+ <div className="device-navigation-bar" style={containerStyle}>
145
+ <div
146
+ style={{
147
+ display: 'flex',
148
+ gap: 64,
149
+ }}
150
+ >
151
+ <div
152
+ style={{ ...buttonStyle, fontSize: 24 }}
153
+ onClick={handleBackButton}
154
+ role="button"
155
+ aria-label="Back"
156
+ >
157
+
158
+ </div>
159
+ <div
160
+ style={{ ...buttonStyle, fontSize: 32, top: -3 }}
161
+ onClick={handleHomeButton}
162
+ role="button"
163
+ aria-label="Home"
164
+ >
165
+
166
+ </div>
167
+ <div
168
+ style={{
169
+ ...buttonStyle,
170
+ fontSize: 20,
171
+ fontWeight: 1000,
172
+ }}
173
+ onClick={handleHomeButton}
174
+ role="button"
175
+ aria-label="Recent Apps"
176
+ >
177
+
178
+ </div>
179
+ </div>
180
+ </div>
181
+ );
182
+ }
183
+
184
+ if (navigationBarType === 'gesture') {
185
+ return (
186
+ <div className="device-navigation-bar" style={containerStyle}>
187
+ <div
188
+ style={{
189
+ ...basePillStyle,
190
+ width: 90,
191
+ backgroundColor: barColor,
192
+ }}
193
+ />
194
+ </div>
195
+ );
196
+ }
197
+
198
+ return null;
199
+ }
200
+
201
+ export default DeviceNavigationBar;
@@ -0,0 +1,85 @@
1
+ import React, { useEffect, useState } from 'react';
2
+ import { Device } from '../types/Device';
3
+
4
+ type DeviceStatusBarProps = {
5
+ height: number;
6
+ backgroundColor: string;
7
+ platform?: Device['platform'];
8
+ isDark: boolean;
9
+ };
10
+
11
+ const formatTime = () =>
12
+ new Date().toLocaleTimeString([], {
13
+ hour: '2-digit',
14
+ minute: '2-digit',
15
+ });
16
+
17
+ const iosIndicators = ['5G', 'Wi-Fi', '92%'];
18
+ const androidIndicators = ['Alarm', 'Wi-Fi', '84%'];
19
+
20
+ export function DeviceStatusBar({
21
+ height,
22
+ backgroundColor,
23
+ platform = 'android',
24
+ isDark,
25
+ }: DeviceStatusBarProps) {
26
+ const [time, setTime] = useState(() => formatTime());
27
+
28
+ useEffect(() => {
29
+ const intervalId = setInterval(() => setTime(formatTime()), 60_000);
30
+ return () => clearInterval(intervalId);
31
+ }, []);
32
+
33
+ const textColor = isDark ? 'rgb(255, 255, 255)' : 'rgb(0, 0, 0)';
34
+ const iconStyle: React.CSSProperties = {
35
+ fontSize: 10,
36
+ letterSpacing: 0.5,
37
+ textTransform: 'uppercase',
38
+ };
39
+
40
+ const containerStyle: React.CSSProperties = {
41
+ position: 'absolute',
42
+ top: 0,
43
+ left: 0,
44
+ right: 0,
45
+ height,
46
+ backgroundColor,
47
+ color: textColor,
48
+ display: 'flex',
49
+ alignItems: 'center',
50
+ fontSize: 12,
51
+ fontWeight: 500,
52
+ padding: '0 12px',
53
+ };
54
+
55
+ if (platform === 'ios') {
56
+ return (
57
+ <div className="device-status-bar" style={containerStyle}>
58
+ <span style={{ fontWeight: 600 }}>{time}</span>
59
+ <div style={{ flex: 1 }} />
60
+ <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
61
+ {iosIndicators.map((indicator) => (
62
+ <span key={indicator} style={iconStyle}>
63
+ {indicator}
64
+ </span>
65
+ ))}
66
+ </div>
67
+ </div>
68
+ );
69
+ }
70
+
71
+ return (
72
+ <div className="device-status-bar" style={containerStyle}>
73
+ <span style={{ flex: 1 }}>{time}</span>
74
+ <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
75
+ {androidIndicators.map((indicator) => (
76
+ <span key={indicator} style={iconStyle}>
77
+ {indicator}
78
+ </span>
79
+ ))}
80
+ </div>
81
+ </div>
82
+ );
83
+ }
84
+
85
+ export default DeviceStatusBar;
@@ -1,41 +1,22 @@
1
- import { useMemo, useState } from 'react';
1
+ import { useState } from 'react';
2
2
  import { Device, getDevices, Node, copyNode } from '..';
3
3
  import { useRenderStore } from '../store';
4
- import { Breadcrumb, BreadcrumbItem } from './Breadcrumb';
5
4
  import { useLogRender } from '../utils/useLogRender';
5
+ import { DeviceButton } from './DeviceButton';
6
+ import { DeviceSelectorModal } from '../modals/DeviceSelectorModal';
6
7
 
7
8
  const devices = getDevices();
8
-
9
9
  interface EditorHeaderProps {
10
10
  onSaveProject?: () => void;
11
+ onRestoreProject?: () => void;
11
12
  current?: Node;
12
13
  editorData?: Node;
13
14
  setEditorData?: (data: Node) => void;
14
15
  }
15
16
 
16
- interface DeviceButtonProps {
17
- device: Device;
18
- selectedDevice: Device | null;
19
- setSelectedDevice: (device: Device) => void;
20
- }
21
- export function DeviceButton({
22
- device,
23
- selectedDevice,
24
- setSelectedDevice,
25
- }: DeviceButtonProps) {
26
- return (
27
- <button
28
- className={`editor-device-button ${selectedDevice === device ? 'editor-device-button--selected' : ''}`}
29
- onClick={() => setSelectedDevice(device)}
30
- >
31
- {device.name} <br />
32
- {device.width}x{device.height}
33
- </button>
34
- );
35
- }
36
-
37
17
  export function EditorHeader({
38
18
  onSaveProject,
19
+ onRestoreProject,
39
20
  current,
40
21
  editorData,
41
22
  setEditorData,
@@ -43,7 +24,7 @@ export function EditorHeader({
43
24
  useLogRender('EditorHeader');
44
25
  const [isDevicesModalOpen, setIsDevicesModalOpen] = useState(false);
45
26
  const copiedNode = useRenderStore((s) => s.copiedNode);
46
- const { device, setDevice } = useRenderStore((s) => ({
27
+ const { device: selectedDevice, setDevice } = useRenderStore((s) => ({
47
28
  device: s.device,
48
29
  setDevice: s.setDevice,
49
30
  }));
@@ -85,7 +66,6 @@ export function EditorHeader({
85
66
  useRenderStore.setState({
86
67
  copiedNode: null,
87
68
  });
88
- useRenderStore.getState().forceRender();
89
69
  setEditorData(updated);
90
70
  };
91
71
  return (
@@ -95,12 +75,12 @@ export function EditorHeader({
95
75
  aria-label="Editor utility header"
96
76
  >
97
77
  <div className="editor-header__devices">
98
- {devices.slice(0, 5).map((device: Device) => (
78
+ {devices.slice(0, 5).map((deviceOption: Device) => (
99
79
  <DeviceButton
100
- key={device.name}
101
- selectedDevice={device}
102
- setSelectedDevice={setDevice}
103
- device={device}
80
+ key={deviceOption.name}
81
+ selectedDevice={selectedDevice}
82
+ onSelect={setDevice}
83
+ device={deviceOption}
104
84
  />
105
85
  ))}
106
86
  <button
@@ -119,13 +99,15 @@ export function EditorHeader({
119
99
  >
120
100
  Save
121
101
  </button>
122
- <button
123
- className="editor-button editor-save-previewconfig-button"
124
- aria-label="Save previewConfig"
125
- onClick={() => useRenderStore.getState().forceRender()}
126
- >
127
- Force Render
128
- </button>
102
+ {onRestoreProject && (
103
+ <button
104
+ className="editor-button editor-save-previewconfig-button"
105
+ aria-label="Restore project data"
106
+ onClick={() => onRestoreProject()}
107
+ >
108
+ Restore
109
+ </button>
110
+ )}
129
111
  <button
130
112
  className="editor-button"
131
113
  aria-label="Copy node"
@@ -144,42 +126,12 @@ export function EditorHeader({
144
126
  )}
145
127
  </div>
146
128
  {isDevicesModalOpen && (
147
- <div
148
- className="editor-modal"
149
- role="dialog"
150
- aria-modal="true"
151
- aria-labelledby="device-selector-title"
152
- >
153
- <div
154
- className="editor-modal__overlay"
155
- onClick={() => setIsDevicesModalOpen(false)}
156
- />
157
- <div className="editor-modal__content">
158
- <div className="editor-modal__header">
159
- <h3 id="device-selector-title">Select a device</h3>
160
- <button
161
- className="editor-button"
162
- aria-label="Close device selector"
163
- onClick={() => setIsDevicesModalOpen(false)}
164
- >
165
- Close
166
- </button>
167
- </div>
168
- <div className="editor-device-grid" role="list">
169
- {devices.map((device: Device) => (
170
- <DeviceButton
171
- key={device.name}
172
- selectedDevice={device}
173
- setSelectedDevice={(d: Device) => {
174
- setDevice(d);
175
- setIsDevicesModalOpen(false);
176
- }}
177
- device={device}
178
- />
179
- ))}
180
- </div>
181
- </div>
182
- </div>
129
+ <DeviceSelectorModal
130
+ devices={devices}
131
+ selectedDevice={selectedDevice}
132
+ onSelect={setDevice}
133
+ onClose={() => setIsDevicesModalOpen(false)}
134
+ />
183
135
  )}
184
136
  </div>
185
137
  );
package/src/index.ts CHANGED
@@ -3,7 +3,7 @@ import AttributesEditor from './AttributesEditor';
3
3
 
4
4
  export type { TargetedScreenSize } from './types/TargetedScreenSize';
5
5
  export type { Node, NodeData, NodeDefaultAttribute } from './types/Node';
6
- export type { Project } from './types/Project';
6
+ export type { Project, ProjectColors } from './types/Project';
7
7
  export {
8
8
  isNodeNullOrUndefined,
9
9
  isNodeString,
@@ -28,7 +28,7 @@ export { extractViewStyle } from './utils/extractViewStyle';
28
28
  export { extractImageStyle } from './utils/extractImageStyle';
29
29
  export { extractTextStyle } from './utils/extractTextStyle';
30
30
  export { ProjectPage } from './pages/ProjectPage';
31
- export type { ProjectPageProps, Tab } from './pages/ProjectPage';
31
+ export type { ProjectPageProps } from './pages/ProjectPage';
32
32
  export { copyNode } from './utils/copyNode';
33
33
  export { logger } from './utils/logger';
34
34
  export type { LogLevel } from './types/Project';