@rebasepro/plugin-insights 0.0.1-canary.eae7889 → 0.1.0

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 (47) hide show
  1. package/dist/core/src/components/BootstrapAdminBanner.d.ts +4 -0
  2. package/dist/core/src/components/LoginView/LoginView.d.ts +22 -0
  3. package/dist/core/src/components/common/useDataTableController.d.ts +3 -3
  4. package/dist/core/src/components/index.d.ts +1 -0
  5. package/dist/core/src/hooks/data/useRelationSelector.d.ts +2 -2
  6. package/dist/core/src/hooks/index.d.ts +1 -0
  7. package/dist/core/src/hooks/useCollapsedGroups.d.ts +16 -1
  8. package/dist/core/src/hooks/useResolvedComponent.d.ts +47 -0
  9. package/dist/index.es.js +314 -214
  10. package/dist/index.es.js.map +1 -1
  11. package/dist/index.umd.js +314 -214
  12. package/dist/index.umd.js.map +1 -1
  13. package/dist/plugin-insights/src/components/CollectionInsightsInline.d.ts +3 -2
  14. package/dist/plugin-insights/src/components/InsightWidget.d.ts +4 -1
  15. package/dist/plugin-insights/src/components/InsightWidgetSkeleton.d.ts +6 -0
  16. package/dist/plugin-insights/src/engine/useInsightsData.d.ts +2 -2
  17. package/dist/plugin-insights/src/types/engine.d.ts +10 -1
  18. package/dist/types/src/controllers/auth.d.ts +8 -2
  19. package/dist/types/src/controllers/client.d.ts +13 -0
  20. package/dist/types/src/controllers/collection_registry.d.ts +2 -1
  21. package/dist/types/src/controllers/data_driver.d.ts +36 -1
  22. package/dist/types/src/controllers/navigation.d.ts +18 -6
  23. package/dist/types/src/controllers/registry.d.ts +9 -1
  24. package/dist/types/src/controllers/side_entity_controller.d.ts +7 -0
  25. package/dist/types/src/rebase_context.d.ts +17 -0
  26. package/dist/types/src/types/backend_hooks.d.ts +187 -0
  27. package/dist/types/src/types/collections.d.ts +31 -11
  28. package/dist/types/src/types/component_ref.d.ts +47 -0
  29. package/dist/types/src/types/cron.d.ts +1 -1
  30. package/dist/types/src/types/entity_views.d.ts +6 -7
  31. package/dist/types/src/types/formex.d.ts +40 -0
  32. package/dist/types/src/types/index.d.ts +3 -0
  33. package/dist/types/src/types/plugins.d.ts +6 -3
  34. package/dist/types/src/types/properties.d.ts +72 -88
  35. package/dist/types/src/types/slots.d.ts +20 -10
  36. package/dist/types/src/types/translations.d.ts +6 -0
  37. package/dist/ui/src/components/FileUpload.d.ts +1 -1
  38. package/dist/ui/src/components/SearchBar.d.ts +5 -1
  39. package/dist/ui/src/styles.d.ts +2 -2
  40. package/package.json +3 -3
  41. package/src/components/CollectionInsightsInline.tsx +10 -2
  42. package/src/components/HomeCardInsightSlot.tsx +5 -1
  43. package/src/components/InsightWidget.tsx +5 -1
  44. package/src/components/InsightWidgetSkeleton.tsx +116 -34
  45. package/src/engine/useInsightsData.ts +5 -5
  46. package/src/types/engine.ts +11 -1
  47. package/src/useInsightsPlugin.tsx +1 -1
@@ -1,4 +1,4 @@
1
- import React from "react";
1
+ import React, { useRef, useState } from "react";
2
2
  import { cls, defaultBorderMixin } from "@rebasepro/ui";
3
3
  import type { ScorecardConfig } from "../types";
4
4
 
@@ -10,6 +10,12 @@ import type { ScorecardConfig } from "../types";
10
10
  * The skeleton receives the scorecard config so it can conditionally
11
11
  * render placeholder lines for comparison, dateRange, and icon —
12
12
  * only when the loaded view will also render them.
13
+ *
14
+ * The standard skeleton mirrors InsightsScorecardView's responsive
15
+ * container-width breakpoints (ResizeObserver → isSmall / isMedium)
16
+ * and uses placeholder heights that exactly match the **computed**
17
+ * Tailwind line-heights (accounting for `leading-*` overrides).
18
+ * This guarantees a pixel-perfect skeleton → loaded transition.
13
19
  */
14
20
  export function InsightWidgetSkeleton({
15
21
  config,
@@ -30,7 +36,7 @@ export function InsightWidgetSkeleton({
30
36
  // Matches InsightsScorecardView compact layout:
31
37
  // container: flex flex-col gap-0.5 px-2.5 py-2 rounded-md border
32
38
  // title: text-[10px] uppercase → line-height ~14px
33
- // value row: text-sm font-semibold → line-height ~20px
39
+ // value row: text-sm font-semibold → line-height 20px
34
40
  // + optional comparison text-[10px] inside value row
35
41
  if (compact) {
36
42
  return (
@@ -63,57 +69,133 @@ export function InsightWidgetSkeleton({
63
69
  }
64
70
 
65
71
  // ── Standard scorecard skeleton ─────────────────────────────────────
66
- // Matches InsightsScorecardView standard layout (isSmall = false):
67
- // container: rounded-lg px-5 py-4 border, minHeight 92
68
- // title row: flex justify-between mb-2
69
- // title: text-xs → line-height ~16px
70
- // dateRange: text-[10px] mt-0.5 → ~14px (optional)
71
- // icon: 18×18 ml-2 (optional)
72
- // value: text-2xl → line-height ~32px
73
- // comparison: text-xs mt-1 → ~16px (optional)
72
+ return <StandardSkeleton
73
+ hasComparison={hasComparison}
74
+ hasIcon={hasIcon}
75
+ hasDateRange={hasDateRange}
76
+ embedded={embedded}
77
+ />;
78
+ }
79
+
80
+ // ── Tailwind line-height reference ──────────────────────────────────────
81
+ // All heights below are the **computed** CSS line-heights, accounting
82
+ // for `leading-*` overrides that InsightsScorecardView applies.
83
+ //
84
+ // Title:
85
+ // text-xs (12px) + leading-snug (1.375) → 12 × 1.375 = 16.5px
86
+ // text-[11px] + leading-snug (1.375) → 11 × 1.375 = 15.125px
87
+ //
88
+ // DateRange:
89
+ // text-[10px] with no explicit LH → normal ≈ 14px (browser)
90
+ //
91
+ // Value:
92
+ // text-2xl (24px) + leading-tight (1.25) → 24 × 1.25 = 30px
93
+ // text-xl (20px) + leading-tight (1.25) → 20 × 1.25 = 25px
94
+ // text-lg (18px) + leading-tight (1.25) → 18 × 1.25 = 22.5px
95
+ //
96
+ // Comparison:
97
+ // text-xs (12px) → built-in LH 1rem = 16px
98
+
99
+ /**
100
+ * Inner component for the standard scorecard skeleton.
101
+ *
102
+ * Mirrors InsightsScorecardView's layout by:
103
+ * 1. Using the same ResizeObserver + containerWidth pattern for
104
+ * responsive breakpoints (isSmall < 200px, isMedium < 300px).
105
+ * 2. Using placeholder heights derived from the exact computed
106
+ * Tailwind line-heights that InsightsScorecardView renders.
107
+ * 3. Matching all container classes, margins, paddings, and flex
108
+ * layout properties identically.
109
+ */
110
+ function StandardSkeleton({
111
+ hasComparison,
112
+ hasIcon,
113
+ hasDateRange,
114
+ embedded,
115
+ }: {
116
+ hasComparison: boolean;
117
+ hasIcon: boolean;
118
+ hasDateRange: boolean;
119
+ embedded: boolean;
120
+ }) {
121
+ const containerRef = useRef<HTMLDivElement>(null);
122
+ const [containerWidth, setContainerWidth] = useState<number | null>(null);
123
+
124
+ React.useLayoutEffect(() => {
125
+ if (!containerRef.current) return;
126
+ setContainerWidth(containerRef.current.offsetWidth);
127
+ const observer = new ResizeObserver((entries) => {
128
+ for (const entry of entries) {
129
+ setContainerWidth(entry.contentRect.width);
130
+ }
131
+ });
132
+ observer.observe(containerRef.current);
133
+ return () => observer.disconnect();
134
+ }, []);
135
+
136
+ // Mirror InsightsScorecardView's responsive breakpoints exactly
137
+ const isSmall = containerWidth !== null && containerWidth < 200;
138
+
139
+ // Computed line-heights for each breakpoint
140
+ // Title: text-xs + leading-snug = 16.5px, text-[11px] + leading-snug = 15.125px
141
+ const titleHeight = isSmall ? 15 : 16.5;
142
+ // Value: leading-tight (×1.25) applied on top of font-size
143
+ const valueHeight = isSmall
144
+ ? 22.5 // text-lg: 18 × 1.25
145
+ : (containerWidth !== null && containerWidth < 300)
146
+ ? 25 // text-xl: 20 × 1.25
147
+ : 30; // text-2xl: 24 × 1.25
148
+ // Comparison: text-xs = 12px / 16px line-height (no leading override)
149
+ const comparisonHeight = 16;
150
+ // Icon: 14px when small, 18px otherwise
151
+ const iconSize = isSmall ? 14 : 18;
152
+
153
+ const baseClass = embedded
154
+ ? `flex flex-col min-w-0 h-full ${isSmall ? "px-3.5 py-3" : "px-5 py-4"}`
155
+ : cls("rounded-lg flex flex-col min-w-0 bg-transparent border", defaultBorderMixin, isSmall ? "px-3.5 py-3" : "px-5 py-4");
156
+
74
157
  return (
75
158
  <div
76
- className={cls(
77
- "animate-pulse",
78
- embedded
79
- ? "h-full px-5 py-4"
80
- : "rounded-lg bg-transparent border px-5 py-4",
81
- !embedded && defaultBorderMixin
82
- )}
83
- style={embedded ? undefined : { minHeight: 92 }}
159
+ ref={containerRef}
160
+ className={cls("animate-pulse", baseClass)}
161
+ style={embedded ? undefined : { minHeight: isSmall ? 68 : 92 }}
84
162
  >
85
- {/* Title row */}
86
- <div className="flex items-center justify-between mb-2">
163
+ {/* Title row — identical flex structure to InsightsScorecardView */}
164
+ <div className={`flex items-center justify-between ${isSmall ? "mb-1" : "mb-2"}`}>
87
165
  <div className="flex flex-col min-w-0">
88
- {/* Title */}
166
+ {/* Title placeholder */}
89
167
  <div className="bg-surface-200 dark:bg-surface-700 rounded"
90
- style={{ height: 16, width: "60%" }}
168
+ style={{ height: titleHeight, width: "60%" }}
91
169
  />
92
- {/* DateRange (only if config has it) */}
93
- {hasDateRange && (
170
+ {/* DateRange hidden when isSmall, same as real view (line 134) */}
171
+ {hasDateRange && !isSmall && (
94
172
  <div className="bg-surface-200/60 dark:bg-surface-700/60 rounded mt-0.5"
95
173
  style={{ height: 14, width: "40%" }}
96
174
  />
97
175
  )}
98
176
  </div>
99
- {/* Icon placeholder (only if config has it) */}
177
+ {/* Icon placeholder same wrapper as real view */}
100
178
  {hasIcon && (
101
- <div className="bg-surface-200 dark:bg-surface-700 rounded ml-2 shrink-0"
102
- style={{ height: 18, width: 18 }}
103
- />
179
+ <span className="ml-2 shrink-0">
180
+ <div className="bg-surface-200 dark:bg-surface-700 rounded"
181
+ style={{ height: iconSize, width: iconSize }}
182
+ />
183
+ </span>
104
184
  )}
105
185
  </div>
106
186
 
107
- {/* Main value */}
187
+ {/* Main value placeholder */}
108
188
  <div className="bg-surface-200 dark:bg-surface-700 rounded"
109
- style={{ height: 32, width: "40%" }}
189
+ style={{ height: valueHeight, width: "40%" }}
110
190
  />
111
191
 
112
- {/* Comparison (only if config has it) */}
192
+ {/* Comparison placeholder */}
113
193
  {hasComparison && (
114
- <div className="bg-surface-200/60 dark:bg-surface-700/60 rounded mt-1"
115
- style={{ height: 16, width: "25%" }}
116
- />
194
+ <div className={isSmall ? "mt-0.5" : "mt-1"}>
195
+ <div className="bg-surface-200/60 dark:bg-surface-700/60 rounded"
196
+ style={{ height: comparisonHeight, width: "25%" }}
197
+ />
198
+ </div>
117
199
  )}
118
200
  </div>
119
201
  );
@@ -1,5 +1,5 @@
1
1
  import { useEffect, useState } from "react";
2
- import type { InsightDefinition, InsightDataResult } from "../types";
2
+ import type { InsightDefinition, InsightDataResult, InsightContext } from "../types";
3
3
  import { useInsightsEngine } from "./InsightsProvider";
4
4
  import { useAuthController } from "@rebasepro/core";
5
5
 
@@ -16,7 +16,7 @@ import { useAuthController } from "@rebasepro/core";
16
16
  */
17
17
  export function useInsightsData(
18
18
  definition: InsightDefinition,
19
- collectionSlug?: string
19
+ context: InsightContext
20
20
  ): {
21
21
  data: InsightDataResult | null;
22
22
  loading: boolean;
@@ -30,7 +30,7 @@ export function useInsightsData(
30
30
  const [loading, setLoading] = useState(true);
31
31
  const [error, setError] = useState<Error | null>(null);
32
32
 
33
- const cacheKey = `${definition.id}:${collectionSlug ?? "global"}`;
33
+ const cacheKey = `${definition.id}:${context.path ?? context.collectionSlug ?? "global"}`;
34
34
 
35
35
  useEffect(() => {
36
36
  // Keep showing skeleton until both auth and engine are ready
@@ -71,7 +71,7 @@ export function useInsightsData(
71
71
  setLoading(true);
72
72
  setError(null);
73
73
 
74
- const promise = definition.data();
74
+ const promise = definition.data(context);
75
75
 
76
76
  cache.setInflight(cacheKey, promise);
77
77
 
@@ -94,7 +94,7 @@ export function useInsightsData(
94
94
  return () => {
95
95
  cancelled = true;
96
96
  };
97
- }, [definition.id, definition.data, collectionSlug, cacheKey, cache, authReady]);
97
+ }, [definition.id, definition.data, context.path, context.collectionSlug, cacheKey, cache, authReady]);
98
98
 
99
99
  return { data, loading, error };
100
100
  }
@@ -1,5 +1,15 @@
1
1
  import type { DataRow, ScorecardConfig } from "./widgets";
2
2
 
3
+ export interface InsightContext {
4
+ /** The resolved path of the collection (e.g., "products/123/orders" or "orders") */
5
+ path?: string;
6
+ parentCollectionSlugs?: string[];
7
+ /** The parent entity IDs if this is a subcollection (e.g., ["123"]) */
8
+ parentEntityIds?: string[];
9
+ /** The collection slug if this is an insight at the collection level */
10
+ collectionSlug?: string;
11
+ }
12
+
3
13
  /**
4
14
  * Result returned by an insight's data callback.
5
15
  */
@@ -45,7 +55,7 @@ export interface InsightDefinition {
45
55
  * }
46
56
  * ```
47
57
  */
48
- data: () => Promise<InsightDataResult>;
58
+ data: (context: InsightContext) => Promise<InsightDataResult>;
49
59
 
50
60
  /** Scorecard field mapping + formatting. */
51
61
  scorecard: ScorecardConfig;
@@ -77,7 +77,7 @@ export function useInsightsPlugin(config: InsightsPluginConfig): RebasePlugin {
77
77
  if (collectionSlug !== slug) return null;
78
78
  return (
79
79
  <CollectionInsightsInline
80
- {...props as { path: string; collection: unknown; parentCollectionIds: string[] }}
80
+ {...props as { path: string; collection: unknown; parentCollectionSlugs: string[], parentEntityIds: string[] }}
81
81
  insights={collectionInsights}
82
82
  />
83
83
  );