@teambit/preview.ui.preview-placeholder 0.0.563 → 0.0.564

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.
@@ -44,9 +44,83 @@ const react_1 = __importStar(require("react"));
44
44
  const compositions_1 = require("@teambit/compositions");
45
45
  const design_ui_heading_1 = require("@teambit/design.ui.heading");
46
46
  const toolbox_string_capitalize_1 = require("@teambit/toolbox.string.capitalize");
47
- const base_ui_loaders_skeleton_1 = require("@teambit/base-ui.loaders.skeleton");
48
47
  const docs_1 = require("@teambit/docs");
49
48
  const preview_placeholder_module_scss_1 = __importDefault(require("./preview-placeholder.module.scss"));
49
+ // ── BrowserSkeleton ─────────────────────────────────────────────────────────
50
+ function BrowserSkeleton() {
51
+ return (react_1.default.createElement("div", { className: preview_placeholder_module_scss_1.default.browserSkeleton },
52
+ react_1.default.createElement("div", { className: preview_placeholder_module_scss_1.default.browserToolbar },
53
+ react_1.default.createElement("span", { className: preview_placeholder_module_scss_1.default.browserDot }),
54
+ react_1.default.createElement("span", { className: preview_placeholder_module_scss_1.default.browserDot }),
55
+ react_1.default.createElement("span", { className: preview_placeholder_module_scss_1.default.browserDot }),
56
+ react_1.default.createElement("div", { className: preview_placeholder_module_scss_1.default.browserUrlBar })),
57
+ react_1.default.createElement("div", { className: preview_placeholder_module_scss_1.default.browserBody },
58
+ react_1.default.createElement("div", { className: preview_placeholder_module_scss_1.default.browserLine1 }),
59
+ react_1.default.createElement("div", { className: preview_placeholder_module_scss_1.default.browserLine2 }),
60
+ react_1.default.createElement("div", { className: preview_placeholder_module_scss_1.default.browserLine3 }),
61
+ react_1.default.createElement("div", { className: preview_placeholder_module_scss_1.default.browserLine4 }))));
62
+ }
63
+ // ── Prefetch helper ─────────────────────────────────────────────────────────
64
+ const prefetchedAssets = new Set();
65
+ function prefetchPreviewAssets(url) {
66
+ if (prefetchedAssets.has(url))
67
+ return;
68
+ prefetchedAssets.add(url);
69
+ fetch(url)
70
+ .then((res) => (res.ok ? res.json() : null))
71
+ .then((data) => {
72
+ if (!(data === null || data === void 0 ? void 0 : data.files))
73
+ return;
74
+ for (const file of data.files) {
75
+ if (document.querySelector(`link[href*="${file}"]`))
76
+ continue;
77
+ const link = document.createElement('link');
78
+ link.rel = 'prefetch';
79
+ link.href = file;
80
+ document.head.appendChild(link);
81
+ }
82
+ })
83
+ .catch(() => { });
84
+ }
85
+ // ── ViewportGate ────────────────────────────────────────────────────────────
86
+ // Defers iframe mounting until near the viewport. Prefetches assets ahead.
87
+ // Does NOT own skeleton state — parent handles that via onLoad.
88
+ function ViewportGate({ previewAssetsUrl, rootMargin = '0px 0px 50% 0px', children, }) {
89
+ const sentinelRef = (0, react_1.useRef)(null);
90
+ const [visible, setVisible] = (0, react_1.useState)(false);
91
+ (0, react_1.useEffect)(() => {
92
+ const el = sentinelRef.current;
93
+ if (!el)
94
+ return;
95
+ // Prefetch observer — warm browser cache 4 viewports ahead
96
+ const prefetchObs = previewAssetsUrl
97
+ ? new IntersectionObserver(([e]) => {
98
+ if (e.isIntersecting) {
99
+ prefetchPreviewAssets(previewAssetsUrl);
100
+ prefetchObs.disconnect();
101
+ }
102
+ }, { rootMargin: '0px 0px 150% 0px' })
103
+ : null;
104
+ // Mount observer — mount iframe when 2 viewports away
105
+ const mountObs = new IntersectionObserver(([e]) => {
106
+ if (e.isIntersecting) {
107
+ mountObs.disconnect();
108
+ prefetchObs === null || prefetchObs === void 0 ? void 0 : prefetchObs.disconnect();
109
+ if (previewAssetsUrl)
110
+ prefetchPreviewAssets(previewAssetsUrl);
111
+ setVisible(true);
112
+ }
113
+ }, { rootMargin });
114
+ prefetchObs === null || prefetchObs === void 0 ? void 0 : prefetchObs.observe(el);
115
+ mountObs.observe(el);
116
+ return () => {
117
+ prefetchObs === null || prefetchObs === void 0 ? void 0 : prefetchObs.disconnect();
118
+ mountObs.disconnect();
119
+ };
120
+ }, []);
121
+ return (react_1.default.createElement("div", { ref: sentinelRef, className: preview_placeholder_module_scss_1.default.viewportGate }, visible ? children : null));
122
+ }
123
+ // ── PreviewPlaceholder ──────────────────────────────────────────────────────
50
124
  function getCompositions(component) {
51
125
  const entry = component.get(compositions_1.CompositionsAspect.id);
52
126
  if (!entry)
@@ -83,6 +157,7 @@ function PreviewPlaceholder(_a) {
83
157
  const serverUrl = (_c = component === null || component === void 0 ? void 0 : component.server) === null || _c === void 0 ? void 0 : _c.url;
84
158
  const prevServerUrlRef = (0, react_1.useRef)(serverUrl);
85
159
  const [forceRender, setForceRender] = react_1.default.useState(0);
160
+ const [previewLoaded, setPreviewLoaded] = (0, react_1.useState)(false);
86
161
  (0, react_1.useEffect)(() => {
87
162
  if (prevServerUrlRef.current !== serverUrl && shouldShowPreview) {
88
163
  prevServerUrlRef.current = serverUrl;
@@ -107,10 +182,12 @@ function PreviewPlaceholder(_a) {
107
182
  const name = component.id.toString();
108
183
  if (!serverUrl || (!shouldShowPreview && component.buildStatus === 'pending'))
109
184
  return (react_1.default.createElement("div", { className: preview_placeholder_module_scss_1.default.previewPlaceholder, "data-tip": "", "data-for": name },
110
- react_1.default.createElement("div", { className: preview_placeholder_module_scss_1.default.skeletonContainer },
111
- react_1.default.createElement(base_ui_loaders_skeleton_1.BlockSkeleton, { lines: 12, className: preview_placeholder_module_scss_1.default.skeletonBlock }))));
112
- return (react_1.default.createElement("div", { key: `${name}-${serverUrl}-${forceRender}` },
113
- react_1.default.createElement(compositions_1.ComponentComposition, { component: component, composition: selectedPreview, pubsub: false, includeEnv: true, loading: 'lazy', viewport: 1280, queryParams: 'disableCta=true' }),
185
+ react_1.default.createElement(BrowserSkeleton, null)));
186
+ return (react_1.default.createElement("div", { key: `${name}-${serverUrl}-${forceRender}`, className: preview_placeholder_module_scss_1.default.previewCard },
187
+ react_1.default.createElement(ViewportGate, { previewAssetsUrl: `/api/${name}/~aspect/preview-assets` },
188
+ react_1.default.createElement(compositions_1.ComponentComposition, { component: component, composition: selectedPreview, pubsub: false, includeEnv: true, loading: 'lazy', viewport: 1280, queryParams: 'disableCta=true', onLoad: () => setPreviewLoaded(true) })),
189
+ !previewLoaded && (react_1.default.createElement("div", { className: preview_placeholder_module_scss_1.default.skeletonOverlay },
190
+ react_1.default.createElement(BrowserSkeleton, null))),
114
191
  react_1.default.createElement("div", { className: preview_placeholder_module_scss_1.default.previewOverlay })));
115
192
  }
116
193
  const PREVIEW_COMPOSITION_SUFFIX = 'Preview';
@@ -1 +1 @@
1
- {"version":3,"file":"preview-placeholder.js","sourceRoot":"","sources":["../preview-placeholder.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"preview-placeholder.js","sourceRoot":"","sources":["../preview-placeholder.tsx"],"names":[],"mappings":"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"}
@@ -78,17 +78,90 @@
78
78
  cursor: pointer;
79
79
  }
80
80
 
81
- .skeletonContainer {
81
+ // ── Viewport gate & skeleton overlay ────────────────────────────────────────
82
+
83
+ .previewCard {
84
+ position: relative;
85
+ width: 100%;
86
+ height: 100%;
87
+ }
88
+
89
+ .viewportGate {
82
90
  width: 100%;
83
91
  height: 100%;
84
- position: relative;
85
- color: var(--skeleton-color, #e0e0e0);
86
- opacity: 0.7;
87
92
  }
88
93
 
89
- .skeletonBlock {
94
+ .skeletonOverlay {
95
+ position: absolute;
96
+ top: 0;
97
+ left: 0;
98
+ width: 100%;
99
+ height: 100%;
100
+ z-index: 10;
101
+ background: var(--surface-color, #ffffff);
102
+ }
103
+
104
+ // ── Browser-chrome skeleton ─────────────────────────────────────────────────
105
+
106
+ .browserSkeleton {
90
107
  width: 100%;
91
108
  height: 100%;
92
109
  min-height: 200px;
93
- display: block;
110
+ display: flex;
111
+ flex-direction: column;
112
+ overflow: hidden;
113
+ }
114
+
115
+ .browserToolbar {
116
+ display: flex;
117
+ align-items: center;
118
+ gap: 5px;
119
+ padding: 7px 10px;
120
+ border-bottom: 1px solid currentColor;
121
+ opacity: 0.08;
122
+ flex-shrink: 0;
123
+ }
124
+
125
+ .browserDot {
126
+ width: 7px;
127
+ height: 7px;
128
+ border-radius: 50%;
129
+ background: currentColor;
130
+ flex-shrink: 0;
131
+ }
132
+
133
+ .browserUrlBar {
134
+ flex: 1;
135
+ height: 14px;
136
+ margin-left: 8px;
137
+ border-radius: 10px;
138
+ background: currentColor;
139
+ }
140
+
141
+ .browserBody {
142
+ flex: 1;
143
+ display: flex;
144
+ flex-direction: column;
145
+ gap: 10px;
146
+ padding: 20px 14px;
147
+ opacity: 0.06;
148
+
149
+ > * {
150
+ height: 10px;
151
+ border-radius: 6px;
152
+ background: currentColor;
153
+ }
154
+ }
155
+
156
+ .browserLine1 {
157
+ width: 55%;
158
+ }
159
+ .browserLine2 {
160
+ width: 75%;
161
+ }
162
+ .browserLine3 {
163
+ width: 40%;
164
+ }
165
+ .browserLine4 {
166
+ width: 65%;
94
167
  }
package/package.json CHANGED
@@ -1,16 +1,15 @@
1
1
  {
2
2
  "name": "@teambit/preview.ui.preview-placeholder",
3
- "version": "0.0.563",
3
+ "version": "0.0.564",
4
4
  "homepage": "https://bit.cloud/teambit/preview/ui/preview-placeholder",
5
5
  "main": "dist/index.js",
6
6
  "componentId": {
7
7
  "scope": "teambit.preview",
8
8
  "name": "ui/preview-placeholder",
9
- "version": "0.0.563"
9
+ "version": "0.0.564"
10
10
  },
11
11
  "dependencies": {
12
12
  "core-js": "^3.0.0",
13
- "@teambit/base-ui.loaders.skeleton": "1.0.1",
14
13
  "@teambit/component-descriptor": "0.0.450",
15
14
  "@teambit/design.ui.heading": "1.0.26",
16
15
  "@teambit/toolbox.string.capitalize": "0.0.508"
@@ -78,17 +78,90 @@
78
78
  cursor: pointer;
79
79
  }
80
80
 
81
- .skeletonContainer {
81
+ // ── Viewport gate & skeleton overlay ────────────────────────────────────────
82
+
83
+ .previewCard {
84
+ position: relative;
85
+ width: 100%;
86
+ height: 100%;
87
+ }
88
+
89
+ .viewportGate {
82
90
  width: 100%;
83
91
  height: 100%;
84
- position: relative;
85
- color: var(--skeleton-color, #e0e0e0);
86
- opacity: 0.7;
87
92
  }
88
93
 
89
- .skeletonBlock {
94
+ .skeletonOverlay {
95
+ position: absolute;
96
+ top: 0;
97
+ left: 0;
98
+ width: 100%;
99
+ height: 100%;
100
+ z-index: 10;
101
+ background: var(--surface-color, #ffffff);
102
+ }
103
+
104
+ // ── Browser-chrome skeleton ─────────────────────────────────────────────────
105
+
106
+ .browserSkeleton {
90
107
  width: 100%;
91
108
  height: 100%;
92
109
  min-height: 200px;
93
- display: block;
110
+ display: flex;
111
+ flex-direction: column;
112
+ overflow: hidden;
113
+ }
114
+
115
+ .browserToolbar {
116
+ display: flex;
117
+ align-items: center;
118
+ gap: 5px;
119
+ padding: 7px 10px;
120
+ border-bottom: 1px solid currentColor;
121
+ opacity: 0.08;
122
+ flex-shrink: 0;
123
+ }
124
+
125
+ .browserDot {
126
+ width: 7px;
127
+ height: 7px;
128
+ border-radius: 50%;
129
+ background: currentColor;
130
+ flex-shrink: 0;
131
+ }
132
+
133
+ .browserUrlBar {
134
+ flex: 1;
135
+ height: 14px;
136
+ margin-left: 8px;
137
+ border-radius: 10px;
138
+ background: currentColor;
139
+ }
140
+
141
+ .browserBody {
142
+ flex: 1;
143
+ display: flex;
144
+ flex-direction: column;
145
+ gap: 10px;
146
+ padding: 20px 14px;
147
+ opacity: 0.06;
148
+
149
+ > * {
150
+ height: 10px;
151
+ border-radius: 6px;
152
+ background: currentColor;
153
+ }
154
+ }
155
+
156
+ .browserLine1 {
157
+ width: 55%;
158
+ }
159
+ .browserLine2 {
160
+ width: 75%;
161
+ }
162
+ .browserLine3 {
163
+ width: 40%;
164
+ }
165
+ .browserLine4 {
166
+ width: 65%;
94
167
  }
@@ -1,14 +1,121 @@
1
1
  import type { ComponentType, ReactNode } from 'react';
2
- import React, { useMemo, useEffect, useRef } from 'react';
2
+ import React, { useMemo, useEffect, useRef, useState } from 'react';
3
3
  import { CompositionsAspect, ComponentComposition, Composition } from '@teambit/compositions';
4
4
  import { H3, H5 } from '@teambit/design.ui.heading';
5
5
  import { capitalize } from '@teambit/toolbox.string.capitalize';
6
6
  import type { ComponentModel } from '@teambit/component';
7
7
  import type { ComponentDescriptor } from '@teambit/component-descriptor';
8
- import { BlockSkeleton } from '@teambit/base-ui.loaders.skeleton';
9
8
  import { DocsAspect } from '@teambit/docs';
10
9
  import styles from './preview-placeholder.module.scss';
11
10
 
11
+ // ── BrowserSkeleton ─────────────────────────────────────────────────────────
12
+
13
+ function BrowserSkeleton() {
14
+ return (
15
+ <div className={styles.browserSkeleton}>
16
+ <div className={styles.browserToolbar}>
17
+ <span className={styles.browserDot} />
18
+ <span className={styles.browserDot} />
19
+ <span className={styles.browserDot} />
20
+ <div className={styles.browserUrlBar} />
21
+ </div>
22
+ <div className={styles.browserBody}>
23
+ <div className={styles.browserLine1} />
24
+ <div className={styles.browserLine2} />
25
+ <div className={styles.browserLine3} />
26
+ <div className={styles.browserLine4} />
27
+ </div>
28
+ </div>
29
+ );
30
+ }
31
+
32
+ // ── Prefetch helper ─────────────────────────────────────────────────────────
33
+
34
+ const prefetchedAssets = new Set<string>();
35
+
36
+ function prefetchPreviewAssets(url: string) {
37
+ if (prefetchedAssets.has(url)) return;
38
+ prefetchedAssets.add(url);
39
+
40
+ fetch(url)
41
+ .then((res) => (res.ok ? res.json() : null))
42
+ .then((data) => {
43
+ if (!data?.files) return;
44
+ for (const file of data.files) {
45
+ if (document.querySelector(`link[href*="${file}"]`)) continue;
46
+ const link = document.createElement('link');
47
+ link.rel = 'prefetch';
48
+ link.href = file;
49
+ document.head.appendChild(link);
50
+ }
51
+ })
52
+ .catch(() => {});
53
+ }
54
+
55
+ // ── ViewportGate ────────────────────────────────────────────────────────────
56
+ // Defers iframe mounting until near the viewport. Prefetches assets ahead.
57
+ // Does NOT own skeleton state — parent handles that via onLoad.
58
+
59
+ function ViewportGate({
60
+ previewAssetsUrl,
61
+ rootMargin = '0px 0px 50% 0px',
62
+ children,
63
+ }: {
64
+ previewAssetsUrl?: string;
65
+ rootMargin?: string;
66
+ children: ReactNode;
67
+ }) {
68
+ const sentinelRef = useRef<HTMLDivElement>(null);
69
+ const [visible, setVisible] = useState(false);
70
+
71
+ useEffect(() => {
72
+ const el = sentinelRef.current;
73
+ if (!el) return;
74
+
75
+ // Prefetch observer — warm browser cache 4 viewports ahead
76
+ const prefetchObs = previewAssetsUrl
77
+ ? new IntersectionObserver(
78
+ ([e]) => {
79
+ if (e.isIntersecting) {
80
+ prefetchPreviewAssets(previewAssetsUrl);
81
+ prefetchObs!.disconnect();
82
+ }
83
+ },
84
+ { rootMargin: '0px 0px 150% 0px' }
85
+ )
86
+ : null;
87
+
88
+ // Mount observer — mount iframe when 2 viewports away
89
+ const mountObs = new IntersectionObserver(
90
+ ([e]) => {
91
+ if (e.isIntersecting) {
92
+ mountObs.disconnect();
93
+ prefetchObs?.disconnect();
94
+ if (previewAssetsUrl) prefetchPreviewAssets(previewAssetsUrl);
95
+ setVisible(true);
96
+ }
97
+ },
98
+ { rootMargin }
99
+ );
100
+
101
+ prefetchObs?.observe(el);
102
+ mountObs.observe(el);
103
+
104
+ return () => {
105
+ prefetchObs?.disconnect();
106
+ mountObs.disconnect();
107
+ };
108
+ }, []);
109
+
110
+ return (
111
+ <div ref={sentinelRef} className={styles.viewportGate}>
112
+ {visible ? children : null}
113
+ </div>
114
+ );
115
+ }
116
+
117
+ // ── PreviewPlaceholder ──────────────────────────────────────────────────────
118
+
12
119
  export function getCompositions(component: ComponentDescriptor) {
13
120
  const entry: any = component.get(CompositionsAspect.id);
14
121
  if (!entry) return [];
@@ -53,6 +160,7 @@ export function PreviewPlaceholder({
53
160
 
54
161
  const prevServerUrlRef = useRef(serverUrl);
55
162
  const [forceRender, setForceRender] = React.useState(0);
163
+ const [previewLoaded, setPreviewLoaded] = useState(false);
56
164
 
57
165
  useEffect(() => {
58
166
  if (prevServerUrlRef.current !== serverUrl && shouldShowPreview) {
@@ -87,23 +195,29 @@ export function PreviewPlaceholder({
87
195
  if (!serverUrl || (!shouldShowPreview && component.buildStatus === 'pending'))
88
196
  return (
89
197
  <div className={styles.previewPlaceholder} data-tip="" data-for={name}>
90
- <div className={styles.skeletonContainer}>
91
- <BlockSkeleton lines={12} className={styles.skeletonBlock} />
92
- </div>
198
+ <BrowserSkeleton />
93
199
  </div>
94
200
  );
95
201
 
96
202
  return (
97
- <div key={`${name}-${serverUrl}-${forceRender}`}>
98
- <ComponentComposition
99
- component={component}
100
- composition={selectedPreview}
101
- pubsub={false}
102
- includeEnv={true}
103
- loading={'lazy'}
104
- viewport={1280}
105
- queryParams={'disableCta=true'}
106
- />
203
+ <div key={`${name}-${serverUrl}-${forceRender}`} className={styles.previewCard}>
204
+ <ViewportGate previewAssetsUrl={`/api/${name}/~aspect/preview-assets`}>
205
+ <ComponentComposition
206
+ component={component}
207
+ composition={selectedPreview}
208
+ pubsub={false}
209
+ includeEnv={true}
210
+ loading={'lazy'}
211
+ viewport={1280}
212
+ queryParams={'disableCta=true'}
213
+ onLoad={() => setPreviewLoaded(true)}
214
+ />
215
+ </ViewportGate>
216
+ {!previewLoaded && (
217
+ <div className={styles.skeletonOverlay}>
218
+ <BrowserSkeleton />
219
+ </div>
220
+ )}
107
221
  <div className={styles.previewOverlay} />
108
222
  </div>
109
223
  );