@teambit/preview.ui.preview-placeholder 0.0.562 → 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,19 +1,18 @@
1
1
  {
2
2
  "name": "@teambit/preview.ui.preview-placeholder",
3
- "version": "0.0.562",
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.562"
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
- "@teambit/design.ui.heading": "1.0.26",
15
13
  "@teambit/component-descriptor": "0.0.450",
16
- "@teambit/toolbox.string.capitalize": "0.0.507"
14
+ "@teambit/design.ui.heading": "1.0.26",
15
+ "@teambit/toolbox.string.capitalize": "0.0.508"
17
16
  },
18
17
  "devDependencies": {
19
18
  "@types/react": "^17.0.8",
@@ -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
  );