@teambit/compositions 1.0.994 → 1.0.995
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.
- package/composition.section.tsx +4 -1
- package/compositions.module.scss +63 -27
- package/compositions.tsx +173 -47
- package/compositions.ui.runtime.tsx +38 -6
- package/dist/composition.section.d.ts +3 -2
- package/dist/composition.section.js +3 -1
- package/dist/composition.section.js.map +1 -1
- package/dist/compositions.d.ts +9 -2
- package/dist/compositions.js +137 -56
- package/dist/compositions.js.map +1 -1
- package/dist/compositions.module.scss +63 -27
- package/dist/compositions.ui.runtime.d.ts +17 -5
- package/dist/compositions.ui.runtime.js +24 -5
- package/dist/compositions.ui.runtime.js.map +1 -1
- package/dist/{preview-1779298653501.js → preview-1779305522985.js} +2 -2
- package/dist/ui/compositions-panel/compositions-panel.d.ts +4 -1
- package/dist/ui/compositions-panel/compositions-panel.js +19 -2
- package/dist/ui/compositions-panel/compositions-panel.js.map +1 -1
- package/dist/ui/compositions-panel/compositions-panel.module.scss +13 -0
- package/dist/ui/compositions-panel/live-control-input.module.scss +6 -7
- package/package.json +20 -20
- package/ui/compositions-panel/compositions-panel.module.scss +13 -0
- package/ui/compositions-panel/compositions-panel.tsx +29 -0
- package/ui/compositions-panel/live-control-input.module.scss +6 -7
package/composition.section.tsx
CHANGED
|
@@ -6,6 +6,7 @@ import type {
|
|
|
6
6
|
CompositionsMenuSlot,
|
|
7
7
|
EmptyStateSlot,
|
|
8
8
|
UsePreviewSandboxSlot,
|
|
9
|
+
UsePreviewPropsSlot,
|
|
9
10
|
} from './compositions.ui.runtime';
|
|
10
11
|
|
|
11
12
|
type Options = { menuBarWidgetSlot: CompositionsMenuSlot };
|
|
@@ -18,7 +19,8 @@ export class CompositionsSection implements Section {
|
|
|
18
19
|
private compositions: CompositionsUI,
|
|
19
20
|
private options: Options,
|
|
20
21
|
private emptyStateSlot: EmptyStateSlot,
|
|
21
|
-
private usePreviewSandboxSlot: UsePreviewSandboxSlot
|
|
22
|
+
private usePreviewSandboxSlot: UsePreviewSandboxSlot,
|
|
23
|
+
private usePreviewPropsSlot: UsePreviewPropsSlot
|
|
22
24
|
) {}
|
|
23
25
|
|
|
24
26
|
navigationLink = {
|
|
@@ -33,6 +35,7 @@ export class CompositionsSection implements Section {
|
|
|
33
35
|
menuBarWidgets={this.options.menuBarWidgetSlot}
|
|
34
36
|
emptyState={this.emptyStateSlot}
|
|
35
37
|
usePreviewSandboxSlot={this.usePreviewSandboxSlot}
|
|
38
|
+
usePreviewPropsSlot={this.usePreviewPropsSlot}
|
|
36
39
|
enableLiveControls
|
|
37
40
|
/>
|
|
38
41
|
),
|
package/compositions.module.scss
CHANGED
|
@@ -146,20 +146,37 @@
|
|
|
146
146
|
border-top: 1px solid var(--bit-border-color-lightest, #eaeaec);
|
|
147
147
|
background: var(--bit-bg-color, #ffffff);
|
|
148
148
|
overflow: hidden;
|
|
149
|
+
min-height: 0;
|
|
150
|
+
// Never overflow the previewArea: if the inline `height` (or a user-set
|
|
151
|
+
// resize) ends up taller than the parent allows (e.g. browser zoom shrinks
|
|
152
|
+
// available space, or there are many controls), `max-height` wins and the
|
|
153
|
+
// tray stays inside its container so `trayBody`'s `overflow-y: auto` keeps
|
|
154
|
+
// working instead of the bottom rows getting clipped off-screen.
|
|
155
|
+
max-height: calc(100% - 80px);
|
|
156
|
+
box-sizing: border-box;
|
|
149
157
|
}
|
|
150
158
|
|
|
151
|
-
.
|
|
159
|
+
.controlsTrayCollapsed {
|
|
160
|
+
// when collapsed, only show the header bar
|
|
161
|
+
height: auto;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// ─── Two-zone header: resize strip on top, click-to-collapse below ─────
|
|
165
|
+
// The two zones are visually separated by a divider so the boundary is
|
|
166
|
+
// obvious: top = "grab to resize", bottom = "click to collapse".
|
|
167
|
+
|
|
168
|
+
.trayResizeStrip {
|
|
169
|
+
position: relative;
|
|
152
170
|
flex-shrink: 0;
|
|
153
|
-
height:
|
|
154
|
-
display: flex;
|
|
155
|
-
align-items: center;
|
|
156
|
-
justify-content: center;
|
|
171
|
+
height: 12px;
|
|
157
172
|
cursor: ns-resize;
|
|
158
|
-
|
|
159
|
-
|
|
173
|
+
user-select: none;
|
|
174
|
+
background: var(--surface01-color, rgba(0, 0, 0, 0.02));
|
|
175
|
+
border-bottom: 1px solid var(--bit-border-color-lightest, #eaeaec);
|
|
176
|
+
transition: background 140ms ease;
|
|
160
177
|
|
|
161
178
|
&:hover {
|
|
162
|
-
background: rgba(0, 0, 0, 0.
|
|
179
|
+
background: var(--surface-hover-color, rgba(0, 0, 0, 0.05));
|
|
163
180
|
}
|
|
164
181
|
|
|
165
182
|
&:hover .trayDragBar {
|
|
@@ -169,19 +186,36 @@
|
|
|
169
186
|
}
|
|
170
187
|
|
|
171
188
|
.trayDragBar {
|
|
189
|
+
position: absolute;
|
|
190
|
+
top: 50%;
|
|
191
|
+
left: 50%;
|
|
192
|
+
transform: translate(-50%, -50%);
|
|
172
193
|
width: 32px;
|
|
173
194
|
height: 3px;
|
|
174
195
|
border-radius: 2px;
|
|
175
196
|
background: var(--bit-border-color-lightest, #d0d0d3);
|
|
176
197
|
transition: all 140ms ease;
|
|
198
|
+
pointer-events: none;
|
|
177
199
|
}
|
|
178
200
|
|
|
179
|
-
.
|
|
180
|
-
flex-shrink: 0;
|
|
201
|
+
.trayHeaderInner {
|
|
181
202
|
display: flex;
|
|
182
203
|
align-items: center;
|
|
183
204
|
justify-content: space-between;
|
|
184
|
-
padding:
|
|
205
|
+
padding: 8px 14px;
|
|
206
|
+
cursor: pointer;
|
|
207
|
+
user-select: none;
|
|
208
|
+
outline: none;
|
|
209
|
+
transition: background 140ms ease;
|
|
210
|
+
|
|
211
|
+
&:hover {
|
|
212
|
+
background: var(--surface-hover-color, rgba(0, 0, 0, 0.03));
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// give the collapsed bar a bit more breathing room
|
|
217
|
+
.controlsTrayCollapsed .trayHeaderInner {
|
|
218
|
+
padding: 10px 14px;
|
|
185
219
|
}
|
|
186
220
|
|
|
187
221
|
.trayTitleRow {
|
|
@@ -218,29 +252,31 @@
|
|
|
218
252
|
line-height: 1;
|
|
219
253
|
}
|
|
220
254
|
|
|
221
|
-
.
|
|
222
|
-
display: flex;
|
|
255
|
+
.trayCollapseIcon {
|
|
256
|
+
display: inline-flex;
|
|
223
257
|
align-items: center;
|
|
224
258
|
justify-content: center;
|
|
225
|
-
width:
|
|
226
|
-
height:
|
|
227
|
-
|
|
228
|
-
border: none;
|
|
229
|
-
background: transparent;
|
|
259
|
+
width: 20px;
|
|
260
|
+
height: 20px;
|
|
261
|
+
font-size: 10px;
|
|
230
262
|
color: var(--bit-text-color-light, #8b8d98);
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
263
|
+
transform: rotate(90deg);
|
|
264
|
+
transform-origin: center;
|
|
265
|
+
transition: transform 200ms ease-in-out;
|
|
266
|
+
pointer-events: none;
|
|
267
|
+
}
|
|
235
268
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
color: var(--bit-text-color-heavy, #1c2024);
|
|
239
|
-
}
|
|
269
|
+
.trayCollapseIconCollapsed {
|
|
270
|
+
transform: rotate(-90deg);
|
|
240
271
|
}
|
|
241
272
|
|
|
242
273
|
.trayBody {
|
|
243
|
-
flex:
|
|
274
|
+
// `flex-basis: auto` so the body contributes its natural content height to
|
|
275
|
+
// the tray's intrinsic size — without this, an auto-sized tray collapses to
|
|
276
|
+
// just the header. `min-height: 0` keeps `overflow-y: auto` working when
|
|
277
|
+
// the parent's `max-height` clamps the tray.
|
|
278
|
+
flex: 1 1 auto;
|
|
279
|
+
min-height: 0;
|
|
244
280
|
overflow-y: auto;
|
|
245
281
|
overflow-x: hidden;
|
|
246
282
|
}
|
package/compositions.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ReactNode } from 'react';
|
|
2
2
|
import React, { useContext, useEffect, useState, useMemo, useRef, useCallback } from 'react';
|
|
3
3
|
import { useParams, useSearchParams } from 'react-router-dom';
|
|
4
|
+
import classNames from 'classnames';
|
|
4
5
|
import head from 'lodash.head';
|
|
5
6
|
import queryString from 'query-string';
|
|
6
7
|
import { ThemeContext } from '@teambit/documenter.theme.theme-context';
|
|
@@ -13,7 +14,12 @@ import { Tab, TabContainer, TabList, TabPanel } from '@teambit/panels';
|
|
|
13
14
|
import { useDocs } from '@teambit/docs.ui.queries.get-docs';
|
|
14
15
|
import { Collapser } from '@teambit/ui-foundation.ui.buttons.collapser';
|
|
15
16
|
import { EmptyBox } from '@teambit/design.ui.empty-box';
|
|
16
|
-
import {
|
|
17
|
+
import type { PreviewIframeAttrs } from '@teambit/preview.ui.component-preview';
|
|
18
|
+
import {
|
|
19
|
+
PreviewPropsAggregator,
|
|
20
|
+
SandboxPermissionsAggregator,
|
|
21
|
+
toPreviewUrl,
|
|
22
|
+
} from '@teambit/preview.ui.component-preview';
|
|
17
23
|
import { useIsMobile } from '@teambit/ui-foundation.ui.hooks.use-is-mobile';
|
|
18
24
|
import { CompositionsMenuBar } from '@teambit/compositions.ui.compositions-menu-bar';
|
|
19
25
|
import { CompositionContextProvider } from '@teambit/compositions.ui.hooks.use-composition';
|
|
@@ -26,8 +32,14 @@ import { OptionButton } from '@teambit/design.ui.input.option-button';
|
|
|
26
32
|
import { StatusMessageCard } from '@teambit/design.ui.surfaces.status-message-card';
|
|
27
33
|
import { Tooltip } from '@teambit/design.ui.tooltip';
|
|
28
34
|
import { Icon } from '@teambit/evangelist.elements.icon';
|
|
35
|
+
import type { UseLiveControlsResult } from '@teambit/compositions.ui.composition-live-controls';
|
|
29
36
|
import { useLiveControls } from '@teambit/compositions.ui.composition-live-controls';
|
|
30
|
-
import type {
|
|
37
|
+
import type {
|
|
38
|
+
EmptyStateSlot,
|
|
39
|
+
CompositionsMenuSlot,
|
|
40
|
+
UsePreviewSandboxSlot,
|
|
41
|
+
UsePreviewPropsSlot,
|
|
42
|
+
} from './compositions.ui.runtime';
|
|
31
43
|
import type { Composition } from './composition';
|
|
32
44
|
import styles from './compositions.module.scss';
|
|
33
45
|
import { ComponentComposition } from './ui';
|
|
@@ -47,6 +59,13 @@ export type CompositionsProp = {
|
|
|
47
59
|
menuBarWidgets?: CompositionsMenuSlot;
|
|
48
60
|
emptyState?: EmptyStateSlot;
|
|
49
61
|
usePreviewSandboxSlot?: UsePreviewSandboxSlot;
|
|
62
|
+
/**
|
|
63
|
+
* per-component resolvers for iframe attributes on the composition preview (`allow`,
|
|
64
|
+
* `referrerPolicy`, ...). Each resolver gets the current `ComponentModel`; results merge
|
|
65
|
+
* with later registrations winning. Default `allow` (`clipboard-write`) lives on
|
|
66
|
+
* `ComponentPreview` and applies when no resolver overrides it.
|
|
67
|
+
*/
|
|
68
|
+
usePreviewPropsSlot?: UsePreviewPropsSlot;
|
|
50
69
|
enableLiveControls?: boolean;
|
|
51
70
|
};
|
|
52
71
|
|
|
@@ -54,6 +73,7 @@ export function Compositions({
|
|
|
54
73
|
menuBarWidgets,
|
|
55
74
|
emptyState,
|
|
56
75
|
usePreviewSandboxSlot,
|
|
76
|
+
usePreviewPropsSlot,
|
|
57
77
|
enableLiveControls = true,
|
|
58
78
|
}: CompositionsProp) {
|
|
59
79
|
const component = useContext(ComponentContext);
|
|
@@ -74,6 +94,8 @@ export function Compositions({
|
|
|
74
94
|
|
|
75
95
|
const properties = useDocs(component.id);
|
|
76
96
|
const previewSandboxHooks = usePreviewSandboxSlot?.values() ?? [];
|
|
97
|
+
const previewPropsHooks = usePreviewPropsSlot?.values() ?? [];
|
|
98
|
+
const [previewAttrs, setPreviewAttrs] = useState<PreviewIframeAttrs>({});
|
|
77
99
|
const isMobile = useIsMobile();
|
|
78
100
|
const showSidebar = !isMobile && component.compositions.length > 0;
|
|
79
101
|
const [isSidebarOpen, setSidebarOpenness] = useState(showSidebar);
|
|
@@ -97,28 +119,50 @@ export function Compositions({
|
|
|
97
119
|
|
|
98
120
|
const queryParams = useMemo(() => queryString.stringify(compositionParams), [compositionParams]);
|
|
99
121
|
|
|
100
|
-
|
|
122
|
+
// Tracks compositions the user has explicitly collapsed. Anything not in
|
|
123
|
+
// this set defaults to expanded, which gives us "auto-open on load" as a
|
|
124
|
+
// pure derivation rather than an effect.
|
|
125
|
+
const [collapsedCompositions, setCollapsedCompositions] = useState<Set<string>>(() => new Set());
|
|
101
126
|
const [isDraggingTray, setIsDraggingTray] = useState(false);
|
|
102
|
-
const trayRef = useRef<HTMLDivElement>(null);
|
|
103
|
-
|
|
127
|
+
const trayRef = useRef<HTMLDivElement | null>(null);
|
|
128
|
+
// `null` = auto-size to content (default). Becomes a number once the user
|
|
129
|
+
// drag-resizes — only then do we pin a fixed height.
|
|
130
|
+
const [trayHeight, setTrayHeight] = useState<number | null>(null);
|
|
104
131
|
const { ready, defs, values, onChange } = useLiveControls();
|
|
105
132
|
|
|
106
|
-
const
|
|
133
|
+
const onResizeStripMouseDown = useCallback((e: React.MouseEvent) => {
|
|
134
|
+
if (e.button !== 0) return;
|
|
107
135
|
e.preventDefault();
|
|
108
136
|
const startY = e.clientY;
|
|
109
|
-
|
|
137
|
+
// Seed the drag from the tray's current rendered height — handles the
|
|
138
|
+
// initial auto-sized case (where `trayHeight` is still null).
|
|
139
|
+
const startHeight = trayRef.current?.offsetHeight ?? 0;
|
|
140
|
+
const parentEl = trayRef.current?.parentElement;
|
|
141
|
+
const parentHeight = parentEl?.clientHeight ?? window.innerHeight;
|
|
142
|
+
const maxHeight = Math.max(120, parentHeight - 80);
|
|
110
143
|
setIsDraggingTray(true);
|
|
111
144
|
document.body.style.cursor = 'ns-resize';
|
|
112
145
|
document.body.style.userSelect = 'none';
|
|
146
|
+
// Mutate the tray's inline height directly and rAF-throttle, so dragging
|
|
147
|
+
// doesn't re-render the whole compositions tree (including the iframe)
|
|
148
|
+
// on every mousemove. State is committed once on mouseup.
|
|
149
|
+
let pendingHeight = startHeight;
|
|
150
|
+
let rafScheduled = false;
|
|
151
|
+
const applyHeight = () => {
|
|
152
|
+
rafScheduled = false;
|
|
153
|
+
if (trayRef.current) trayRef.current.style.height = `${pendingHeight}px`;
|
|
154
|
+
};
|
|
113
155
|
const onMove = (ev: MouseEvent) => {
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
if (
|
|
117
|
-
|
|
156
|
+
const delta = startY - ev.clientY;
|
|
157
|
+
pendingHeight = Math.max(120, Math.min(maxHeight, startHeight + delta));
|
|
158
|
+
if (!rafScheduled) {
|
|
159
|
+
rafScheduled = true;
|
|
160
|
+
requestAnimationFrame(applyHeight);
|
|
118
161
|
}
|
|
119
162
|
};
|
|
120
163
|
const onUp = () => {
|
|
121
164
|
setIsDraggingTray(false);
|
|
165
|
+
setTrayHeight(pendingHeight);
|
|
122
166
|
document.body.style.cursor = '';
|
|
123
167
|
document.body.style.userSelect = '';
|
|
124
168
|
document.removeEventListener('mousemove', onMove);
|
|
@@ -148,7 +192,21 @@ export function Compositions({
|
|
|
148
192
|
}, [enableLiveControls]);
|
|
149
193
|
|
|
150
194
|
const currentCompositionHasControls = ready && defs.length > 0;
|
|
151
|
-
const
|
|
195
|
+
const currentCompositionIdentifier = currentComposition?.identifier;
|
|
196
|
+
const isTrayCollapsedForCurrent =
|
|
197
|
+
!!currentCompositionIdentifier && collapsedCompositions.has(currentCompositionIdentifier);
|
|
198
|
+
const showControlsTray = currentCompositionHasControls;
|
|
199
|
+
const isTrayCollapsed = showControlsTray && isTrayCollapsedForCurrent;
|
|
200
|
+
|
|
201
|
+
const toggleTrayCollapsed = useCallback(() => {
|
|
202
|
+
if (!currentCompositionIdentifier) return;
|
|
203
|
+
setCollapsedCompositions((prev) => {
|
|
204
|
+
const next = new Set(prev);
|
|
205
|
+
if (next.has(currentCompositionIdentifier)) next.delete(currentCompositionIdentifier);
|
|
206
|
+
else next.add(currentCompositionIdentifier);
|
|
207
|
+
return next;
|
|
208
|
+
});
|
|
209
|
+
}, [currentCompositionIdentifier]);
|
|
152
210
|
|
|
153
211
|
return (
|
|
154
212
|
<CompositionContextProvider queryParams={compositionParams} setQueryParams={setCompositionParams}>
|
|
@@ -160,25 +218,21 @@ export function Compositions({
|
|
|
160
218
|
<OptionButton icon="open-tab" />
|
|
161
219
|
</Link>
|
|
162
220
|
</Tooltip>
|
|
163
|
-
{currentCompositionHasControls && (
|
|
164
|
-
<Tooltip content={controlsTrayOpen ? 'Hide controls' : 'Show controls'} placement="bottom">
|
|
165
|
-
<button
|
|
166
|
-
className={`${styles.toolbarButton} ${controlsTrayOpen ? styles.toolbarButtonActive : ''}`}
|
|
167
|
-
onClick={() => setControlsTrayOpen((o) => !o)}
|
|
168
|
-
>
|
|
169
|
-
<Icon of="settings" />
|
|
170
|
-
</button>
|
|
171
|
-
</Tooltip>
|
|
172
|
-
)}
|
|
173
221
|
</CompositionsMenuBar>
|
|
174
222
|
<SandboxPermissionsAggregator
|
|
175
223
|
hooks={previewSandboxHooks}
|
|
176
224
|
onSandboxChange={setSandboxValue}
|
|
177
225
|
component={component}
|
|
178
226
|
/>
|
|
227
|
+
<PreviewPropsAggregator
|
|
228
|
+
hooks={previewPropsHooks}
|
|
229
|
+
onPreviewPropsChange={setPreviewAttrs}
|
|
230
|
+
component={component}
|
|
231
|
+
/>
|
|
179
232
|
<div className={styles.previewArea}>
|
|
180
233
|
{isDraggingTray && <div className={styles.dragOverlay} />}
|
|
181
234
|
<CompositionContent
|
|
235
|
+
{...previewAttrs}
|
|
182
236
|
className={styles.compositionPanel}
|
|
183
237
|
emptyState={emptyState}
|
|
184
238
|
component={component}
|
|
@@ -187,28 +241,17 @@ export function Compositions({
|
|
|
187
241
|
sandbox={sandboxValue}
|
|
188
242
|
/>
|
|
189
243
|
{showControlsTray && (
|
|
190
|
-
<
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
<Icon of="x" />
|
|
202
|
-
</button>
|
|
203
|
-
</div>
|
|
204
|
-
<div className={styles.trayBody}>
|
|
205
|
-
{ready ? (
|
|
206
|
-
<LiveControls defs={defs} values={values} onChange={onChange} />
|
|
207
|
-
) : (
|
|
208
|
-
<div className={styles.trayEmpty}>No live controls available for this composition</div>
|
|
209
|
-
)}
|
|
210
|
-
</div>
|
|
211
|
-
</div>
|
|
244
|
+
<LiveControlsTray
|
|
245
|
+
trayRef={trayRef}
|
|
246
|
+
collapsed={isTrayCollapsed}
|
|
247
|
+
height={trayHeight}
|
|
248
|
+
ready={ready}
|
|
249
|
+
defs={defs}
|
|
250
|
+
values={values}
|
|
251
|
+
onChange={onChange}
|
|
252
|
+
onResizeStripMouseDown={onResizeStripMouseDown}
|
|
253
|
+
onToggleExpanded={toggleTrayCollapsed}
|
|
254
|
+
/>
|
|
212
255
|
)}
|
|
213
256
|
</div>
|
|
214
257
|
</Pane>
|
|
@@ -234,6 +277,9 @@ export function Compositions({
|
|
|
234
277
|
isScaling={isScaling}
|
|
235
278
|
useNameParam={useNameParam}
|
|
236
279
|
includesEnvTemplate={component.preview?.includesEnvTemplate}
|
|
280
|
+
hasLiveControls={currentCompositionHasControls}
|
|
281
|
+
liveControlsActive={currentCompositionHasControls && !isTrayCollapsedForCurrent}
|
|
282
|
+
onToggleLiveControls={toggleTrayCollapsed}
|
|
237
283
|
onSelectComposition={(composition) => {
|
|
238
284
|
if (!currentComposition || !location) return;
|
|
239
285
|
const selectedCompositionFromUrl = params['*'];
|
|
@@ -252,9 +298,6 @@ export function Compositions({
|
|
|
252
298
|
}
|
|
253
299
|
const newPath = pathSegments.join('/');
|
|
254
300
|
navigate(`/${newPath}?${urlParams.toString()}`);
|
|
255
|
-
// open the tray only on explicit user selection, so the
|
|
256
|
-
// tray stays closed on navigation/tab return.
|
|
257
|
-
setControlsTrayOpen(true);
|
|
258
301
|
}}
|
|
259
302
|
url={compositionUrl}
|
|
260
303
|
compositions={component.compositions}
|
|
@@ -272,6 +315,89 @@ export function Compositions({
|
|
|
272
315
|
);
|
|
273
316
|
}
|
|
274
317
|
|
|
318
|
+
type LiveControlsTrayProps = {
|
|
319
|
+
trayRef: React.RefObject<HTMLDivElement | null>;
|
|
320
|
+
collapsed: boolean;
|
|
321
|
+
height: number | null;
|
|
322
|
+
ready: boolean;
|
|
323
|
+
defs: UseLiveControlsResult['defs'];
|
|
324
|
+
values: UseLiveControlsResult['values'];
|
|
325
|
+
onChange: UseLiveControlsResult['onChange'];
|
|
326
|
+
onResizeStripMouseDown: (e: React.MouseEvent) => void;
|
|
327
|
+
onToggleExpanded: () => void;
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
function LiveControlsTray({
|
|
331
|
+
trayRef,
|
|
332
|
+
collapsed,
|
|
333
|
+
height,
|
|
334
|
+
ready,
|
|
335
|
+
defs,
|
|
336
|
+
values,
|
|
337
|
+
onChange,
|
|
338
|
+
onResizeStripMouseDown,
|
|
339
|
+
onToggleExpanded,
|
|
340
|
+
}: LiveControlsTrayProps) {
|
|
341
|
+
// height = null → let CSS size the tray to its content (with the max-height
|
|
342
|
+
// clamp doing the upper bound); height = number → user has drag-resized,
|
|
343
|
+
// pin to that.
|
|
344
|
+
const trayStyle = collapsed || height === null ? undefined : { height };
|
|
345
|
+
return (
|
|
346
|
+
<div
|
|
347
|
+
ref={trayRef}
|
|
348
|
+
className={classNames(styles.controlsTray, collapsed && styles.controlsTrayCollapsed)}
|
|
349
|
+
style={trayStyle}
|
|
350
|
+
>
|
|
351
|
+
{!collapsed && (
|
|
352
|
+
<div
|
|
353
|
+
className={styles.trayResizeStrip}
|
|
354
|
+
onMouseDown={onResizeStripMouseDown}
|
|
355
|
+
role="separator"
|
|
356
|
+
aria-orientation="horizontal"
|
|
357
|
+
aria-label="Resize live controls"
|
|
358
|
+
title="Drag to resize"
|
|
359
|
+
>
|
|
360
|
+
<div className={styles.trayDragBar} />
|
|
361
|
+
</div>
|
|
362
|
+
)}
|
|
363
|
+
<div
|
|
364
|
+
className={styles.trayHeaderInner}
|
|
365
|
+
onClick={onToggleExpanded}
|
|
366
|
+
role="button"
|
|
367
|
+
tabIndex={0}
|
|
368
|
+
onKeyDown={(e) => {
|
|
369
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
370
|
+
e.preventDefault();
|
|
371
|
+
onToggleExpanded();
|
|
372
|
+
}
|
|
373
|
+
}}
|
|
374
|
+
title={collapsed ? 'Click to expand' : 'Click to collapse'}
|
|
375
|
+
>
|
|
376
|
+
<div className={styles.trayTitleRow}>
|
|
377
|
+
<Icon of="settings" className={styles.trayIcon} />
|
|
378
|
+
<span className={styles.trayTitle}>Live Controls</span>
|
|
379
|
+
{ready && defs.length > 0 && <span className={styles.trayBadge}>{defs.length}</span>}
|
|
380
|
+
</div>
|
|
381
|
+
<span
|
|
382
|
+
className={classNames(styles.trayCollapseIcon, collapsed && styles.trayCollapseIconCollapsed)}
|
|
383
|
+
aria-hidden
|
|
384
|
+
>
|
|
385
|
+
<Icon of="right-rounded-corners" />
|
|
386
|
+
</span>
|
|
387
|
+
</div>
|
|
388
|
+
{!collapsed && (
|
|
389
|
+
<div className={styles.trayBody}>
|
|
390
|
+
{ready ? (
|
|
391
|
+
<LiveControls defs={defs} values={values} onChange={onChange} />
|
|
392
|
+
) : (
|
|
393
|
+
<div className={styles.trayEmpty}>No live controls available for this composition</div>
|
|
394
|
+
)}
|
|
395
|
+
</div>
|
|
396
|
+
)}
|
|
397
|
+
</div>
|
|
398
|
+
);
|
|
399
|
+
}
|
|
400
|
+
|
|
275
401
|
export type CompositionContentProps = {
|
|
276
402
|
component: ComponentModel;
|
|
277
403
|
selected?: Composition;
|
|
@@ -9,7 +9,7 @@ import { CompositionCompareSection } from '@teambit/compositions.ui.composition-
|
|
|
9
9
|
import { CompositionCompare } from '@teambit/compositions.ui.composition-compare';
|
|
10
10
|
import type { ComponentCompareUI } from '@teambit/component-compare';
|
|
11
11
|
import { ComponentCompareAspect } from '@teambit/component-compare';
|
|
12
|
-
import type { UseSandboxPermission } from '@teambit/preview.ui.component-preview';
|
|
12
|
+
import type { UsePreviewProps, UseSandboxPermission } from '@teambit/preview.ui.component-preview';
|
|
13
13
|
import { CompositionsSection } from './composition.section';
|
|
14
14
|
import { CompositionsAspect } from './compositions.aspect';
|
|
15
15
|
import type { MenuBarWidget } from './compositions';
|
|
@@ -18,12 +18,14 @@ import { CompositionContent } from './compositions';
|
|
|
18
18
|
export type CompositionsMenuSlot = SlotRegistry<MenuBarWidget[]>;
|
|
19
19
|
export type EmptyStateSlot = SlotRegistry<ComponentType>;
|
|
20
20
|
export type UsePreviewSandboxSlot = SlotRegistry<UseSandboxPermission>;
|
|
21
|
+
export type UsePreviewPropsSlot = SlotRegistry<UsePreviewProps>;
|
|
21
22
|
|
|
22
23
|
export class CompositionsUI {
|
|
23
24
|
constructor(
|
|
24
25
|
private menuBarWidgetSlot: CompositionsMenuSlot,
|
|
25
26
|
private emptyStateSlot: EmptyStateSlot,
|
|
26
|
-
private usePreviewSandboxSlot: UsePreviewSandboxSlot
|
|
27
|
+
private usePreviewSandboxSlot: UsePreviewSandboxSlot,
|
|
28
|
+
private usePreviewPropsSlot: UsePreviewPropsSlot
|
|
27
29
|
) {}
|
|
28
30
|
/**
|
|
29
31
|
* register a new tester empty state. this allows to register a different empty state from each environment for example.
|
|
@@ -41,6 +43,18 @@ export class CompositionsUI {
|
|
|
41
43
|
this.usePreviewSandboxSlot.register(useSandboxPermission);
|
|
42
44
|
}
|
|
43
45
|
|
|
46
|
+
/**
|
|
47
|
+
* register a per-component resolver for iframe attributes on the composition preview
|
|
48
|
+
* (`allow`, `referrerPolicy`, ...). The resolver runs at render time with the current
|
|
49
|
+
* `ComponentModel`; results from multiple resolvers merge with later keys winning. Use
|
|
50
|
+
* this to grant a specific subset of components extra Permissions Policy capabilities
|
|
51
|
+
* (e.g. `fullscreen` for chart/video components) without loosening the default for all
|
|
52
|
+
* components.
|
|
53
|
+
*/
|
|
54
|
+
registerPreviewProps(usePreviewProps: UsePreviewProps) {
|
|
55
|
+
this.usePreviewPropsSlot.register(usePreviewProps);
|
|
56
|
+
}
|
|
57
|
+
|
|
44
58
|
getCompositionsCompare = () => {
|
|
45
59
|
return (
|
|
46
60
|
<CompositionCompare
|
|
@@ -54,23 +68,35 @@ export class CompositionsUI {
|
|
|
54
68
|
|
|
55
69
|
static dependencies = [ComponentAspect, ComponentCompareAspect];
|
|
56
70
|
static runtime = UIRuntime;
|
|
57
|
-
static slots = [
|
|
71
|
+
static slots = [
|
|
72
|
+
Slot.withType<ReactNode>(),
|
|
73
|
+
Slot.withType<ComponentType>(),
|
|
74
|
+
Slot.withType<UseSandboxPermission>(),
|
|
75
|
+
Slot.withType<UsePreviewProps>(),
|
|
76
|
+
];
|
|
58
77
|
|
|
59
78
|
static async provider(
|
|
60
79
|
[component, componentCompare]: [ComponentUI, ComponentCompareUI],
|
|
61
80
|
config: {},
|
|
62
|
-
[compositionMenuSlot, emptyStateSlot, usePreviewSandboxSlot]: [
|
|
81
|
+
[compositionMenuSlot, emptyStateSlot, usePreviewSandboxSlot, usePreviewPropsSlot]: [
|
|
63
82
|
CompositionsMenuSlot,
|
|
64
83
|
EmptyStateSlot,
|
|
65
84
|
UsePreviewSandboxSlot,
|
|
85
|
+
UsePreviewPropsSlot,
|
|
66
86
|
]
|
|
67
87
|
) {
|
|
68
|
-
const compositions = new CompositionsUI(
|
|
88
|
+
const compositions = new CompositionsUI(
|
|
89
|
+
compositionMenuSlot,
|
|
90
|
+
emptyStateSlot,
|
|
91
|
+
usePreviewSandboxSlot,
|
|
92
|
+
usePreviewPropsSlot
|
|
93
|
+
);
|
|
69
94
|
const section = new CompositionsSection(
|
|
70
95
|
compositions,
|
|
71
96
|
{ menuBarWidgetSlot: compositions.menuBarWidgetSlot },
|
|
72
97
|
emptyStateSlot,
|
|
73
|
-
usePreviewSandboxSlot
|
|
98
|
+
usePreviewSandboxSlot,
|
|
99
|
+
usePreviewPropsSlot
|
|
74
100
|
);
|
|
75
101
|
const compositionCompare = new CompositionCompareSection(compositions);
|
|
76
102
|
component.registerRoute(section.route);
|
|
@@ -83,6 +109,12 @@ export class CompositionsUI {
|
|
|
83
109
|
manager.add('allow-same-origin');
|
|
84
110
|
}
|
|
85
111
|
});
|
|
112
|
+
// Default Permissions Policy: allow clipboard writes so copy-to-clipboard buttons in
|
|
113
|
+
// compositions work. Clipboard-read, camera, mic, geolocation, etc. remain denied. Other
|
|
114
|
+
// aspects may layer on (e.g. `fullscreen` for chart/video components).
|
|
115
|
+
compositions.registerPreviewProps((manager) => {
|
|
116
|
+
manager.set('allow', 'clipboard-write');
|
|
117
|
+
});
|
|
86
118
|
return compositions;
|
|
87
119
|
}
|
|
88
120
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Section } from '@teambit/component';
|
|
2
|
-
import type { CompositionsUI, CompositionsMenuSlot, EmptyStateSlot, UsePreviewSandboxSlot } from './compositions.ui.runtime';
|
|
2
|
+
import type { CompositionsUI, CompositionsMenuSlot, EmptyStateSlot, UsePreviewSandboxSlot, UsePreviewPropsSlot } from './compositions.ui.runtime';
|
|
3
3
|
type Options = {
|
|
4
4
|
menuBarWidgetSlot: CompositionsMenuSlot;
|
|
5
5
|
};
|
|
@@ -11,11 +11,12 @@ export declare class CompositionsSection implements Section {
|
|
|
11
11
|
private options;
|
|
12
12
|
private emptyStateSlot;
|
|
13
13
|
private usePreviewSandboxSlot;
|
|
14
|
+
private usePreviewPropsSlot;
|
|
14
15
|
constructor(
|
|
15
16
|
/**
|
|
16
17
|
* simulations ui extension.
|
|
17
18
|
*/
|
|
18
|
-
compositions: CompositionsUI, options: Options, emptyStateSlot: EmptyStateSlot, usePreviewSandboxSlot: UsePreviewSandboxSlot);
|
|
19
|
+
compositions: CompositionsUI, options: Options, emptyStateSlot: EmptyStateSlot, usePreviewSandboxSlot: UsePreviewSandboxSlot, usePreviewPropsSlot: UsePreviewPropsSlot);
|
|
19
20
|
navigationLink: {
|
|
20
21
|
href: string;
|
|
21
22
|
children: string;
|
|
@@ -27,11 +27,12 @@ class CompositionsSection {
|
|
|
27
27
|
/**
|
|
28
28
|
* simulations ui extension.
|
|
29
29
|
*/
|
|
30
|
-
compositions, options, emptyStateSlot, usePreviewSandboxSlot) {
|
|
30
|
+
compositions, options, emptyStateSlot, usePreviewSandboxSlot, usePreviewPropsSlot) {
|
|
31
31
|
this.compositions = compositions;
|
|
32
32
|
this.options = options;
|
|
33
33
|
this.emptyStateSlot = emptyStateSlot;
|
|
34
34
|
this.usePreviewSandboxSlot = usePreviewSandboxSlot;
|
|
35
|
+
this.usePreviewPropsSlot = usePreviewPropsSlot;
|
|
35
36
|
_defineProperty(this, "navigationLink", {
|
|
36
37
|
href: '~compositions',
|
|
37
38
|
children: 'Preview'
|
|
@@ -42,6 +43,7 @@ class CompositionsSection {
|
|
|
42
43
|
menuBarWidgets: this.options.menuBarWidgetSlot,
|
|
43
44
|
emptyState: this.emptyStateSlot,
|
|
44
45
|
usePreviewSandboxSlot: this.usePreviewSandboxSlot,
|
|
46
|
+
usePreviewPropsSlot: this.usePreviewPropsSlot,
|
|
45
47
|
enableLiveControls: true
|
|
46
48
|
})
|
|
47
49
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["_react","data","_interopRequireDefault","require","_compositions","e","__esModule","default","_defineProperty","r","t","_toPropertyKey","Object","defineProperty","value","enumerable","configurable","writable","i","_toPrimitive","Symbol","toPrimitive","call","TypeError","String","Number","CompositionsSection","constructor","compositions","options","emptyStateSlot","usePreviewSandboxSlot","href","children","path","element","createElement","Compositions","menuBarWidgets","menuBarWidgetSlot","emptyState","enableLiveControls","exports"],"sources":["composition.section.tsx"],"sourcesContent":["import type { Section } from '@teambit/component';\nimport React from 'react';\nimport { Compositions } from './compositions';\nimport type {\n CompositionsUI,\n CompositionsMenuSlot,\n EmptyStateSlot,\n UsePreviewSandboxSlot,\n} from './compositions.ui.runtime';\n\ntype Options = { menuBarWidgetSlot: CompositionsMenuSlot };\n\nexport class CompositionsSection implements Section {\n constructor(\n /**\n * simulations ui extension.\n */\n private compositions: CompositionsUI,\n private options: Options,\n private emptyStateSlot: EmptyStateSlot,\n private usePreviewSandboxSlot: UsePreviewSandboxSlot\n ) {}\n\n navigationLink = {\n href: '~compositions',\n children: 'Preview',\n };\n\n route = {\n path: '~compositions/*',\n element: (\n <Compositions\n menuBarWidgets={this.options.menuBarWidgetSlot}\n emptyState={this.emptyStateSlot}\n usePreviewSandboxSlot={this.usePreviewSandboxSlot}\n enableLiveControls\n />\n ),\n };\n\n order = 20;\n}\n"],"mappings":";;;;;;AACA,SAAAA,OAAA;EAAA,MAAAC,IAAA,GAAAC,sBAAA,CAAAC,OAAA;EAAAH,MAAA,YAAAA,CAAA;IAAA,OAAAC,IAAA;EAAA;EAAA,OAAAA,IAAA;AAAA;AACA,SAAAG,cAAA;EAAA,MAAAH,IAAA,GAAAE,OAAA;EAAAC,aAAA,YAAAA,CAAA;IAAA,OAAAH,IAAA;EAAA;EAAA,OAAAA,IAAA;AAAA;AAA8C,SAAAC,uBAAAG,CAAA,WAAAA,CAAA,IAAAA,CAAA,CAAAC,UAAA,GAAAD,CAAA,KAAAE,OAAA,EAAAF,CAAA;AAAA,SAAAG,gBAAAH,CAAA,EAAAI,CAAA,EAAAC,CAAA,YAAAD,CAAA,GAAAE,cAAA,CAAAF,CAAA,MAAAJ,CAAA,GAAAO,MAAA,CAAAC,cAAA,CAAAR,CAAA,EAAAI,CAAA,IAAAK,KAAA,EAAAJ,CAAA,EAAAK,UAAA,MAAAC,YAAA,MAAAC,QAAA,UAAAZ,CAAA,CAAAI,CAAA,IAAAC,CAAA,EAAAL,CAAA;AAAA,SAAAM,eAAAD,CAAA,QAAAQ,CAAA,GAAAC,YAAA,CAAAT,CAAA,uCAAAQ,CAAA,GAAAA,CAAA,GAAAA,CAAA;AAAA,SAAAC,aAAAT,CAAA,EAAAD,CAAA,2BAAAC,CAAA,KAAAA,CAAA,SAAAA,CAAA,MAAAL,CAAA,GAAAK,CAAA,CAAAU,MAAA,CAAAC,WAAA,kBAAAhB,CAAA,QAAAa,CAAA,GAAAb,CAAA,CAAAiB,IAAA,CAAAZ,CAAA,EAAAD,CAAA,uCAAAS,CAAA,SAAAA,CAAA,YAAAK,SAAA,yEAAAd,CAAA,GAAAe,MAAA,GAAAC,MAAA,EAAAf,CAAA;
|
|
1
|
+
{"version":3,"names":["_react","data","_interopRequireDefault","require","_compositions","e","__esModule","default","_defineProperty","r","t","_toPropertyKey","Object","defineProperty","value","enumerable","configurable","writable","i","_toPrimitive","Symbol","toPrimitive","call","TypeError","String","Number","CompositionsSection","constructor","compositions","options","emptyStateSlot","usePreviewSandboxSlot","usePreviewPropsSlot","href","children","path","element","createElement","Compositions","menuBarWidgets","menuBarWidgetSlot","emptyState","enableLiveControls","exports"],"sources":["composition.section.tsx"],"sourcesContent":["import type { Section } from '@teambit/component';\nimport React from 'react';\nimport { Compositions } from './compositions';\nimport type {\n CompositionsUI,\n CompositionsMenuSlot,\n EmptyStateSlot,\n UsePreviewSandboxSlot,\n UsePreviewPropsSlot,\n} from './compositions.ui.runtime';\n\ntype Options = { menuBarWidgetSlot: CompositionsMenuSlot };\n\nexport class CompositionsSection implements Section {\n constructor(\n /**\n * simulations ui extension.\n */\n private compositions: CompositionsUI,\n private options: Options,\n private emptyStateSlot: EmptyStateSlot,\n private usePreviewSandboxSlot: UsePreviewSandboxSlot,\n private usePreviewPropsSlot: UsePreviewPropsSlot\n ) {}\n\n navigationLink = {\n href: '~compositions',\n children: 'Preview',\n };\n\n route = {\n path: '~compositions/*',\n element: (\n <Compositions\n menuBarWidgets={this.options.menuBarWidgetSlot}\n emptyState={this.emptyStateSlot}\n usePreviewSandboxSlot={this.usePreviewSandboxSlot}\n usePreviewPropsSlot={this.usePreviewPropsSlot}\n enableLiveControls\n />\n ),\n };\n\n order = 20;\n}\n"],"mappings":";;;;;;AACA,SAAAA,OAAA;EAAA,MAAAC,IAAA,GAAAC,sBAAA,CAAAC,OAAA;EAAAH,MAAA,YAAAA,CAAA;IAAA,OAAAC,IAAA;EAAA;EAAA,OAAAA,IAAA;AAAA;AACA,SAAAG,cAAA;EAAA,MAAAH,IAAA,GAAAE,OAAA;EAAAC,aAAA,YAAAA,CAAA;IAAA,OAAAH,IAAA;EAAA;EAAA,OAAAA,IAAA;AAAA;AAA8C,SAAAC,uBAAAG,CAAA,WAAAA,CAAA,IAAAA,CAAA,CAAAC,UAAA,GAAAD,CAAA,KAAAE,OAAA,EAAAF,CAAA;AAAA,SAAAG,gBAAAH,CAAA,EAAAI,CAAA,EAAAC,CAAA,YAAAD,CAAA,GAAAE,cAAA,CAAAF,CAAA,MAAAJ,CAAA,GAAAO,MAAA,CAAAC,cAAA,CAAAR,CAAA,EAAAI,CAAA,IAAAK,KAAA,EAAAJ,CAAA,EAAAK,UAAA,MAAAC,YAAA,MAAAC,QAAA,UAAAZ,CAAA,CAAAI,CAAA,IAAAC,CAAA,EAAAL,CAAA;AAAA,SAAAM,eAAAD,CAAA,QAAAQ,CAAA,GAAAC,YAAA,CAAAT,CAAA,uCAAAQ,CAAA,GAAAA,CAAA,GAAAA,CAAA;AAAA,SAAAC,aAAAT,CAAA,EAAAD,CAAA,2BAAAC,CAAA,KAAAA,CAAA,SAAAA,CAAA,MAAAL,CAAA,GAAAK,CAAA,CAAAU,MAAA,CAAAC,WAAA,kBAAAhB,CAAA,QAAAa,CAAA,GAAAb,CAAA,CAAAiB,IAAA,CAAAZ,CAAA,EAAAD,CAAA,uCAAAS,CAAA,SAAAA,CAAA,YAAAK,SAAA,yEAAAd,CAAA,GAAAe,MAAA,GAAAC,MAAA,EAAAf,CAAA;AAWvC,MAAMgB,mBAAmB,CAAoB;EAClDC,WAAWA;EACT;AACJ;AACA;EACYC,YAA4B,EAC5BC,OAAgB,EAChBC,cAA8B,EAC9BC,qBAA4C,EAC5CC,mBAAwC,EAChD;IAAA,KALQJ,YAA4B,GAA5BA,YAA4B;IAAA,KAC5BC,OAAgB,GAAhBA,OAAgB;IAAA,KAChBC,cAA8B,GAA9BA,cAA8B;IAAA,KAC9BC,qBAA4C,GAA5CA,qBAA4C;IAAA,KAC5CC,mBAAwC,GAAxCA,mBAAwC;IAAAxB,eAAA,yBAGjC;MACfyB,IAAI,EAAE,eAAe;MACrBC,QAAQ,EAAE;IACZ,CAAC;IAAA1B,eAAA,gBAEO;MACN2B,IAAI,EAAE,iBAAiB;MACvBC,OAAO,eACLpC,MAAA,GAAAO,OAAA,CAAA8B,aAAA,CAACjC,aAAA,GAAAkC,YAAY;QACXC,cAAc,EAAE,IAAI,CAACV,OAAO,CAACW,iBAAkB;QAC/CC,UAAU,EAAE,IAAI,CAACX,cAAe;QAChCC,qBAAqB,EAAE,IAAI,CAACA,qBAAsB;QAClDC,mBAAmB,EAAE,IAAI,CAACA,mBAAoB;QAC9CU,kBAAkB;MAAA,CACnB;IAEL,CAAC;IAAAlC,eAAA,gBAEO,EAAE;EApBP;AAqBL;AAACmC,OAAA,CAAAjB,mBAAA,GAAAA,mBAAA","ignoreList":[]}
|