@bravostudioai/react 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/dist/_virtual/main.js +3 -2
  2. package/dist/cli/commands/generate.js +161 -1438
  3. package/dist/cli/commands/generate.js.map +1 -1
  4. package/dist/codegen/generator.js +473 -0
  5. package/dist/codegen/generator.js.map +1 -0
  6. package/dist/codegen/parser.js +720 -0
  7. package/dist/codegen/parser.js.map +1 -0
  8. package/dist/components/EncoreApp.js +197 -162
  9. package/dist/components/EncoreApp.js.map +1 -1
  10. package/dist/contexts/EncoreRouterContext.js +13 -0
  11. package/dist/contexts/EncoreRouterContext.js.map +1 -0
  12. package/dist/hooks/usePusherUpdates.js +4 -2
  13. package/dist/hooks/usePusherUpdates.js.map +1 -1
  14. package/dist/lib/dynamicModules.js +75 -85
  15. package/dist/lib/dynamicModules.js.map +1 -1
  16. package/dist/lib/moduleRegistry.js +20 -0
  17. package/dist/lib/moduleRegistry.js.map +1 -0
  18. package/dist/lib/packages.js +1 -3
  19. package/dist/lib/packages.js.map +1 -1
  20. package/dist/src/cli/commands/generate.d.ts.map +1 -1
  21. package/dist/src/codegen/generator.d.ts +10 -0
  22. package/dist/src/codegen/generator.d.ts.map +1 -0
  23. package/dist/src/codegen/index.d.ts +4 -0
  24. package/dist/src/codegen/index.d.ts.map +1 -0
  25. package/dist/src/codegen/parser.d.ts +37 -0
  26. package/dist/src/codegen/parser.d.ts.map +1 -0
  27. package/dist/src/codegen/types.d.ts +53 -0
  28. package/dist/src/codegen/types.d.ts.map +1 -0
  29. package/dist/src/components/EncoreApp.d.ts +5 -1
  30. package/dist/src/components/EncoreApp.d.ts.map +1 -1
  31. package/dist/src/contexts/EncoreRouterContext.d.ts +10 -0
  32. package/dist/src/contexts/EncoreRouterContext.d.ts.map +1 -0
  33. package/dist/src/hooks/useAuthRedirect.d.ts.map +1 -1
  34. package/dist/src/lib/dynamicModules.d.ts +1 -5
  35. package/dist/src/lib/dynamicModules.d.ts.map +1 -1
  36. package/dist/src/lib/moduleRegistry.d.ts +9 -0
  37. package/dist/src/lib/moduleRegistry.d.ts.map +1 -0
  38. package/dist/src/lib/packages.d.ts.map +1 -1
  39. package/package.json +1 -1
  40. package/src/cli/commands/generate.ts +88 -2723
  41. package/src/codegen/generator.ts +877 -0
  42. package/src/codegen/index.ts +3 -0
  43. package/src/codegen/parser.ts +1614 -0
  44. package/src/codegen/types.ts +58 -0
  45. package/src/components/EncoreApp.tsx +75 -22
  46. package/src/contexts/EncoreRouterContext.ts +28 -0
  47. package/src/hooks/useAuthRedirect.ts +56 -55
  48. package/src/lib/packages.ts +8 -15
@@ -0,0 +1,58 @@
1
+ export interface ComponentInfo {
2
+ id: string;
3
+ name: string;
4
+ type: string;
5
+ tags: string[];
6
+ propName: string; // Sanitized name for prop
7
+ propType: string; // TypeScript type for the prop
8
+ }
9
+
10
+ export interface ArrayContainerInfo {
11
+ id: string;
12
+ name: string;
13
+ propName: string;
14
+ components: ComponentInfo[];
15
+ }
16
+
17
+ export interface SliderInfo {
18
+ id: string;
19
+ name: string;
20
+ arrayContainer: ArrayContainerInfo | null;
21
+ }
22
+
23
+ export interface InputGroupInfo {
24
+ groupName: string;
25
+ groupType: string; // "single" for radio button behavior
26
+ elements: Array<{
27
+ id: string;
28
+ name: string;
29
+ }>;
30
+ }
31
+
32
+ export interface FormInfo {
33
+ formId: string;
34
+ formName: string;
35
+ submitButtonId?: string;
36
+ inputs: Array<{
37
+ id: string;
38
+ name: string;
39
+ type: string;
40
+ propName: string; // Sanitized and qualified prop name
41
+ _parentPath?: string[]; // Temporary: parent path for qualification
42
+ }>;
43
+ }
44
+
45
+ export interface SelectInputInfo {
46
+ id: string;
47
+ name: string;
48
+ propName: string; // e.g., "contractType" for value prop, generates onContractTypeChange handler
49
+ _parentPath?: string[]; // For qualification if duplicates
50
+ }
51
+
52
+ export interface ActionButtonInfo {
53
+ id: string;
54
+ name: string;
55
+ propName: string; // e.g., "bookButton" generates onBookButtonClick handler
56
+ actionType: string; // "remote", "link", etc.
57
+ _parentPath?: string[];
58
+ }
@@ -19,15 +19,35 @@ import EncoreRepeatingContainerContext, {
19
19
  type RepeatingContainerControl,
20
20
  } from "../contexts/EncoreRepeatingContainerContext";
21
21
  import DynamicComponent from "./DynamicComponent";
22
- import { Link } from "react-router-dom";
22
+ // import { Link } from "react-router-dom"; // Removed dependency
23
+ import { useEncoreRouter } from "../contexts/EncoreRouterContext";
23
24
  import { usePusherUpdates } from "../hooks/usePusherUpdates";
24
25
 
26
+ // Simple internal Link component that uses our router context
27
+ const Link = ({ to, children, style, ...props }: any) => {
28
+ const { navigate } = useEncoreRouter();
29
+ return (
30
+ <a
31
+ href={to}
32
+ onClick={(e) => {
33
+ e.preventDefault();
34
+ navigate(to);
35
+ }}
36
+ style={{ cursor: "pointer", ...style }}
37
+ {...props}
38
+ >
39
+ {children}
40
+ </a>
41
+ );
42
+ };
43
+
25
44
  type Props = {
26
45
  appId: string;
27
46
  pageId?: string;
28
47
  componentId?: string;
29
48
  fallback?: React.ReactNode;
30
49
  onSizeChange?: (size: { width: number; height: number }) => void;
50
+ onContentSizeChange?: (size: { width: number; height: number }) => void;
31
51
  onAction?: (payload: EncoreActionPayload) => void | Promise<void>;
32
52
  data?: Record<string, string | number | any[]>;
33
53
  // When provided, force the runtime to load from either remote or local sources
@@ -66,6 +86,7 @@ const EncoreApp = ({
66
86
  componentId,
67
87
  fallback,
68
88
  onSizeChange,
89
+ onContentSizeChange,
69
90
  onAction,
70
91
  data,
71
92
  source,
@@ -102,6 +123,31 @@ const EncoreApp = ({
102
123
  const setPageId = useEncoreState(setPageIdSelector);
103
124
  const assetsById = useEncoreState(assetsByIdSelector);
104
125
  const containerRef = useRef<HTMLDivElement | null>(null);
126
+ const contentWrapperRef = useRef<HTMLDivElement | null>(null);
127
+
128
+ // Monitor content size changes
129
+ useEffect(() => {
130
+ if (!onContentSizeChange) return;
131
+ const element = contentWrapperRef.current;
132
+ if (!element) return;
133
+
134
+ const notify = () => {
135
+ // Use scroll dimensions to get full size including overflow
136
+ onContentSizeChange({
137
+ width: element.scrollWidth,
138
+ height: element.scrollHeight,
139
+ });
140
+ };
141
+
142
+ const observer = new ResizeObserver(() => {
143
+ notify();
144
+ });
145
+
146
+ // Emit initial size
147
+ notify();
148
+ observer.observe(element);
149
+ return () => observer.disconnect();
150
+ }, [onContentSizeChange]);
105
151
 
106
152
  // State to force DynamicComponent reload when updates are received
107
153
  const [reloadKey, setReloadKey] = useState<string | number>(0);
@@ -730,28 +776,35 @@ const EncoreApp = ({
730
776
  return (
731
777
  <div
732
778
  ref={containerRef}
733
- style={{ width: "100%", height: "100%", position: "relative" }}
779
+ style={{
780
+ width: "100%",
781
+ height: "100%",
782
+ position: "relative",
783
+ overflow: "hidden",
784
+ }}
734
785
  >
735
- <Suspense fallback={fallback || <div />}>
736
- <EncoreComponentIdContext.Provider value={{ componentId }}>
737
- <EncoreActionContext.Provider value={{ onAction }}>
738
- <EncoreRepeatingContainerContext.Provider
739
- value={repeatingContainerContextValue}
740
- >
741
- <EncoreBindingContext.Provider value={context}>
742
- <DynamicComponent
743
- name={`${appId}/draft/components/${pageId}`}
744
- fallback={fallback}
745
- reloadKey={reloadKey}
746
- componentCode={componentCode}
747
- >
748
- {" "}
749
- </DynamicComponent>
750
- </EncoreBindingContext.Provider>
751
- </EncoreRepeatingContainerContext.Provider>
752
- </EncoreActionContext.Provider>
753
- </EncoreComponentIdContext.Provider>
754
- </Suspense>
786
+ <div ref={contentWrapperRef} style={{ width: "100%", minHeight: "100%" }}>
787
+ <Suspense fallback={fallback || <div />}>
788
+ <EncoreComponentIdContext.Provider value={{ componentId }}>
789
+ <EncoreActionContext.Provider value={{ onAction }}>
790
+ <EncoreRepeatingContainerContext.Provider
791
+ value={repeatingContainerContextValue}
792
+ >
793
+ <EncoreBindingContext.Provider value={context}>
794
+ <DynamicComponent
795
+ name={`${appId}/draft/components/${pageId}`}
796
+ fallback={fallback}
797
+ reloadKey={reloadKey}
798
+ componentCode={componentCode}
799
+ >
800
+ {" "}
801
+ </DynamicComponent>
802
+ </EncoreBindingContext.Provider>
803
+ </EncoreRepeatingContainerContext.Provider>
804
+ </EncoreActionContext.Provider>
805
+ </EncoreComponentIdContext.Provider>
806
+ </Suspense>
807
+ </div>
755
808
  </div>
756
809
  );
757
810
  };
@@ -0,0 +1,28 @@
1
+ import React, { useContext } from "react";
2
+
3
+ export type EncoreRouterContextType = {
4
+ navigate: (path: string) => void;
5
+ pathname: string;
6
+ searchParams: URLSearchParams;
7
+ };
8
+
9
+ // Default implementation using window for basic fallback
10
+ const defaultRouter: EncoreRouterContextType = {
11
+ navigate: (path: string) => {
12
+ if (typeof window !== "undefined") {
13
+ window.location.href = path;
14
+ }
15
+ },
16
+ pathname: typeof window !== "undefined" ? window.location.pathname : "",
17
+ searchParams:
18
+ typeof window !== "undefined"
19
+ ? new URLSearchParams(window.location.search)
20
+ : new URLSearchParams(),
21
+ };
22
+
23
+ const EncoreRouterContext =
24
+ React.createContext<EncoreRouterContextType>(defaultRouter);
25
+
26
+ export const useEncoreRouter = () => useContext(EncoreRouterContext);
27
+
28
+ export default EncoreRouterContext;
@@ -1,63 +1,64 @@
1
1
  import EncoreAppContext from "../contexts/EncoreAppContext";
2
2
  import useEncoreState from "../stores/useEncoreState";
3
- import { useSearchParams, useNavigate, useLocation } from "react-router-dom";
3
+ import { useEncoreRouter } from "../contexts/EncoreRouterContext";
4
4
  import { useContext, useEffect, useMemo } from "react";
5
5
 
6
6
  const useAuthRedirect = () => {
7
- const [searchParams] = useSearchParams();
8
- const location = useLocation();
9
- const navigate = useNavigate();
10
-
11
- const appId =
12
- searchParams.get("appId") ||
13
- location.pathname.split("/apps/")[1]?.split("/")[0];
14
- const pageId =
15
- searchParams.get("pageId") ||
16
- location.pathname.split("/pages/")[1]?.split("/")[0];
17
- const noRedirect = searchParams.get("noRedirect");
18
-
19
- const { app } = useContext(EncoreAppContext);
20
- const accessToken = useEncoreState((state) => state.accessToken);
21
-
22
- const navMap = app.data?.app?.navigationMap;
23
- const loginPageId = app.data?.app?.loginPageId;
24
- const isAuthenticated = accessToken && accessToken?.expireAt > Date.now();
25
-
26
- // Some apps have multiple pages in the signin flow.
27
- // Existing apps don't have a "page requires auth" declaration (or the inverse) so we must infer it.
28
- // We infer it using pages linked from the tagged login page
29
- const loginPages = useMemo(() => {
30
- if (!navMap) return [];
31
-
32
- const findConnectedPages = (
33
- pageMap: Record<string, { linksTo: string[] }>,
34
- currentPage: string,
35
- visited = new Set(),
36
- ): string[] => {
37
- if (visited.has(currentPage)) return [];
38
- visited.add(currentPage);
39
-
40
- const directLinks = pageMap[currentPage]?.linksTo || [];
41
- const indirectLinks = directLinks.flatMap((link) =>
42
- findConnectedPages(pageMap, link, visited),
43
- );
44
-
45
- return [...directLinks, ...indirectLinks];
46
- };
47
-
48
- const result = new Set(findConnectedPages(navMap.pages, loginPageId));
49
- result.add(loginPageId);
50
-
51
- return Array.from(result);
52
- }, [navMap]);
53
-
54
- useEffect(() => {
55
- if (noRedirect) return;
56
- if (isAuthenticated) return;
57
- if (!loginPageId || loginPages.includes(pageId as string)) return;
58
-
59
- navigate(`/apps/${appId}/pages/${loginPageId}`);
60
- }, [loginPageId, pageId, isAuthenticated, noRedirect]);
7
+ const { searchParams, pathname, navigate } = useEncoreRouter();
8
+
9
+ // Polyfill location object style access if needed, or just use pathname directly since that's all that WAS used
10
+ const location = { pathname };
11
+
12
+ const appId =
13
+ searchParams.get("appId") ||
14
+ location.pathname.split("/apps/")[1]?.split("/")[0];
15
+ const pageId =
16
+ searchParams.get("pageId") ||
17
+ location.pathname.split("/pages/")[1]?.split("/")[0];
18
+ const noRedirect = searchParams.get("noRedirect");
19
+
20
+ const { app } = useContext(EncoreAppContext);
21
+ const accessToken = useEncoreState((state) => state.accessToken);
22
+
23
+ const navMap = app.data?.app?.navigationMap;
24
+ const loginPageId = app.data?.app?.loginPageId;
25
+ const isAuthenticated = accessToken && accessToken?.expireAt > Date.now();
26
+
27
+ // Some apps have multiple pages in the signin flow.
28
+ // Existing apps don't have a "page requires auth" declaration (or the inverse) so we must infer it.
29
+ // We infer it using pages linked from the tagged login page
30
+ const loginPages = useMemo(() => {
31
+ if (!navMap) return [];
32
+
33
+ const findConnectedPages = (
34
+ pageMap: Record<string, { linksTo: string[] }>,
35
+ currentPage: string,
36
+ visited = new Set()
37
+ ): string[] => {
38
+ if (visited.has(currentPage)) return [];
39
+ visited.add(currentPage);
40
+
41
+ const directLinks = pageMap[currentPage]?.linksTo || [];
42
+ const indirectLinks = directLinks.flatMap((link) =>
43
+ findConnectedPages(pageMap, link, visited)
44
+ );
45
+
46
+ return [...directLinks, ...indirectLinks];
47
+ };
48
+
49
+ const result = new Set(findConnectedPages(navMap.pages, loginPageId));
50
+ result.add(loginPageId);
51
+
52
+ return Array.from(result);
53
+ }, [navMap]);
54
+
55
+ useEffect(() => {
56
+ if (noRedirect) return;
57
+ if (isAuthenticated) return;
58
+ if (!loginPageId || loginPages.includes(pageId as string)) return;
59
+
60
+ navigate(`/apps/${appId}/pages/${loginPageId}`);
61
+ }, [loginPageId, pageId, isAuthenticated, noRedirect]);
61
62
  };
62
63
 
63
64
  export default useAuthRedirect;
@@ -1,33 +1,26 @@
1
1
  import React from "react";
2
- // import {
3
- // Link,
4
- // useSearchParams,
5
- // useNavigate,
6
- // useLocation,
7
- // } from "react-router-dom";
2
+ // React router dom dependency removed
8
3
  import axios from "axios";
9
4
  import EncoreComponents from "../components";
10
- import * as EncoreApp from "../app"
5
+ import * as EncoreApp from "../app";
11
6
  // Note: ../app module doesn't exist in encore-lib, commenting out for now
12
7
 
13
8
  export type Package = {
14
- exports: unknown;
9
+ exports: unknown;
15
10
  };
16
11
 
17
12
  // These are the packages that will be available to remotely loaded code
18
13
  const Packages: Record<string, () => Package> = {
19
- "@/bravo/app": () => ({ exports: EncoreApp }), // Disabled: app module doesn't exist
20
- // "react-router-dom": () => ({
21
- // exports: { Link, useSearchParams, useNavigate, useLocation },
22
- // }),
23
- axios: () => ({ exports: axios }),
24
- react: () => ({ exports: React }),
14
+ "@/bravo/app": () => ({ exports: EncoreApp }), // Disabled: app module doesn't exist
15
+ // React router dom removed
16
+ axios: () => ({ exports: axios }),
17
+ react: () => ({ exports: React }),
25
18
  };
26
19
 
27
20
  // Enable this if you want to use components from local project
28
21
  const USE_LOCAL_BRAVO_RN = true;
29
22
  if (USE_LOCAL_BRAVO_RN) {
30
- Packages["@/bravo/components"] = () => ({ exports: EncoreComponents });
23
+ Packages["@/bravo/components"] = () => ({ exports: EncoreComponents });
31
24
  }
32
25
 
33
26
  export default Packages;