@teambit/compositions 1.0.994 → 1.0.996
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 +178 -55
- 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 +135 -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-1779737067779.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/dist/ui/compositions-panel/overlay.module.scss +4 -0
- 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/ui/compositions-panel/overlay.module.scss +4 -0
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,11 @@ 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 {
|
|
18
|
+
PreviewPropsAggregator,
|
|
19
|
+
SandboxPermissionsAggregator,
|
|
20
|
+
toPreviewUrl,
|
|
21
|
+
} from '@teambit/preview.ui.component-preview';
|
|
17
22
|
import { useIsMobile } from '@teambit/ui-foundation.ui.hooks.use-is-mobile';
|
|
18
23
|
import { CompositionsMenuBar } from '@teambit/compositions.ui.compositions-menu-bar';
|
|
19
24
|
import { CompositionContextProvider } from '@teambit/compositions.ui.hooks.use-composition';
|
|
@@ -26,8 +31,14 @@ import { OptionButton } from '@teambit/design.ui.input.option-button';
|
|
|
26
31
|
import { StatusMessageCard } from '@teambit/design.ui.surfaces.status-message-card';
|
|
27
32
|
import { Tooltip } from '@teambit/design.ui.tooltip';
|
|
28
33
|
import { Icon } from '@teambit/evangelist.elements.icon';
|
|
34
|
+
import type { UseLiveControlsResult } from '@teambit/compositions.ui.composition-live-controls';
|
|
29
35
|
import { useLiveControls } from '@teambit/compositions.ui.composition-live-controls';
|
|
30
|
-
import type {
|
|
36
|
+
import type {
|
|
37
|
+
EmptyStateSlot,
|
|
38
|
+
CompositionsMenuSlot,
|
|
39
|
+
UsePreviewSandboxSlot,
|
|
40
|
+
UsePreviewPropsSlot,
|
|
41
|
+
} from './compositions.ui.runtime';
|
|
31
42
|
import type { Composition } from './composition';
|
|
32
43
|
import styles from './compositions.module.scss';
|
|
33
44
|
import { ComponentComposition } from './ui';
|
|
@@ -47,6 +58,13 @@ export type CompositionsProp = {
|
|
|
47
58
|
menuBarWidgets?: CompositionsMenuSlot;
|
|
48
59
|
emptyState?: EmptyStateSlot;
|
|
49
60
|
usePreviewSandboxSlot?: UsePreviewSandboxSlot;
|
|
61
|
+
/**
|
|
62
|
+
* per-component resolvers for iframe attributes on the composition preview (`allow`,
|
|
63
|
+
* `referrerPolicy`, ...). Each resolver gets the current `ComponentModel`; results merge
|
|
64
|
+
* with later registrations winning. Default `allow` (`clipboard-write`) lives on
|
|
65
|
+
* `ComponentPreview` and applies when no resolver overrides it.
|
|
66
|
+
*/
|
|
67
|
+
usePreviewPropsSlot?: UsePreviewPropsSlot;
|
|
50
68
|
enableLiveControls?: boolean;
|
|
51
69
|
};
|
|
52
70
|
|
|
@@ -54,6 +72,7 @@ export function Compositions({
|
|
|
54
72
|
menuBarWidgets,
|
|
55
73
|
emptyState,
|
|
56
74
|
usePreviewSandboxSlot,
|
|
75
|
+
usePreviewPropsSlot,
|
|
57
76
|
enableLiveControls = true,
|
|
58
77
|
}: CompositionsProp) {
|
|
59
78
|
const component = useContext(ComponentContext);
|
|
@@ -74,6 +93,7 @@ export function Compositions({
|
|
|
74
93
|
|
|
75
94
|
const properties = useDocs(component.id);
|
|
76
95
|
const previewSandboxHooks = usePreviewSandboxSlot?.values() ?? [];
|
|
96
|
+
const previewPropsHooks = usePreviewPropsSlot?.values() ?? [];
|
|
77
97
|
const isMobile = useIsMobile();
|
|
78
98
|
const showSidebar = !isMobile && component.compositions.length > 0;
|
|
79
99
|
const [isSidebarOpen, setSidebarOpenness] = useState(showSidebar);
|
|
@@ -97,28 +117,50 @@ export function Compositions({
|
|
|
97
117
|
|
|
98
118
|
const queryParams = useMemo(() => queryString.stringify(compositionParams), [compositionParams]);
|
|
99
119
|
|
|
100
|
-
|
|
120
|
+
// Tracks compositions the user has explicitly collapsed. Anything not in
|
|
121
|
+
// this set defaults to expanded, which gives us "auto-open on load" as a
|
|
122
|
+
// pure derivation rather than an effect.
|
|
123
|
+
const [collapsedCompositions, setCollapsedCompositions] = useState<Set<string>>(() => new Set());
|
|
101
124
|
const [isDraggingTray, setIsDraggingTray] = useState(false);
|
|
102
|
-
const trayRef = useRef<HTMLDivElement>(null);
|
|
103
|
-
|
|
125
|
+
const trayRef = useRef<HTMLDivElement | null>(null);
|
|
126
|
+
// `null` = auto-size to content (default). Becomes a number once the user
|
|
127
|
+
// drag-resizes — only then do we pin a fixed height.
|
|
128
|
+
const [trayHeight, setTrayHeight] = useState<number | null>(null);
|
|
104
129
|
const { ready, defs, values, onChange } = useLiveControls();
|
|
105
130
|
|
|
106
|
-
const
|
|
131
|
+
const onResizeStripMouseDown = useCallback((e: React.MouseEvent) => {
|
|
132
|
+
if (e.button !== 0) return;
|
|
107
133
|
e.preventDefault();
|
|
108
134
|
const startY = e.clientY;
|
|
109
|
-
|
|
135
|
+
// Seed the drag from the tray's current rendered height — handles the
|
|
136
|
+
// initial auto-sized case (where `trayHeight` is still null).
|
|
137
|
+
const startHeight = trayRef.current?.offsetHeight ?? 0;
|
|
138
|
+
const parentEl = trayRef.current?.parentElement;
|
|
139
|
+
const parentHeight = parentEl?.clientHeight ?? window.innerHeight;
|
|
140
|
+
const maxHeight = Math.max(120, parentHeight - 80);
|
|
110
141
|
setIsDraggingTray(true);
|
|
111
142
|
document.body.style.cursor = 'ns-resize';
|
|
112
143
|
document.body.style.userSelect = 'none';
|
|
144
|
+
// Mutate the tray's inline height directly and rAF-throttle, so dragging
|
|
145
|
+
// doesn't re-render the whole compositions tree (including the iframe)
|
|
146
|
+
// on every mousemove. State is committed once on mouseup.
|
|
147
|
+
let pendingHeight = startHeight;
|
|
148
|
+
let rafScheduled = false;
|
|
149
|
+
const applyHeight = () => {
|
|
150
|
+
rafScheduled = false;
|
|
151
|
+
if (trayRef.current) trayRef.current.style.height = `${pendingHeight}px`;
|
|
152
|
+
};
|
|
113
153
|
const onMove = (ev: MouseEvent) => {
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
if (
|
|
117
|
-
|
|
154
|
+
const delta = startY - ev.clientY;
|
|
155
|
+
pendingHeight = Math.max(120, Math.min(maxHeight, startHeight + delta));
|
|
156
|
+
if (!rafScheduled) {
|
|
157
|
+
rafScheduled = true;
|
|
158
|
+
requestAnimationFrame(applyHeight);
|
|
118
159
|
}
|
|
119
160
|
};
|
|
120
161
|
const onUp = () => {
|
|
121
162
|
setIsDraggingTray(false);
|
|
163
|
+
setTrayHeight(pendingHeight);
|
|
122
164
|
document.body.style.cursor = '';
|
|
123
165
|
document.body.style.userSelect = '';
|
|
124
166
|
document.removeEventListener('mousemove', onMove);
|
|
@@ -148,7 +190,21 @@ export function Compositions({
|
|
|
148
190
|
}, [enableLiveControls]);
|
|
149
191
|
|
|
150
192
|
const currentCompositionHasControls = ready && defs.length > 0;
|
|
151
|
-
const
|
|
193
|
+
const currentCompositionIdentifier = currentComposition?.identifier;
|
|
194
|
+
const isTrayCollapsedForCurrent =
|
|
195
|
+
!!currentCompositionIdentifier && collapsedCompositions.has(currentCompositionIdentifier);
|
|
196
|
+
const showControlsTray = currentCompositionHasControls;
|
|
197
|
+
const isTrayCollapsed = showControlsTray && isTrayCollapsedForCurrent;
|
|
198
|
+
|
|
199
|
+
const toggleTrayCollapsed = useCallback(() => {
|
|
200
|
+
if (!currentCompositionIdentifier) return;
|
|
201
|
+
setCollapsedCompositions((prev) => {
|
|
202
|
+
const next = new Set(prev);
|
|
203
|
+
if (next.has(currentCompositionIdentifier)) next.delete(currentCompositionIdentifier);
|
|
204
|
+
else next.add(currentCompositionIdentifier);
|
|
205
|
+
return next;
|
|
206
|
+
});
|
|
207
|
+
}, [currentCompositionIdentifier]);
|
|
152
208
|
|
|
153
209
|
return (
|
|
154
210
|
<CompositionContextProvider queryParams={compositionParams} setQueryParams={setCompositionParams}>
|
|
@@ -160,16 +216,6 @@ export function Compositions({
|
|
|
160
216
|
<OptionButton icon="open-tab" />
|
|
161
217
|
</Link>
|
|
162
218
|
</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
219
|
</CompositionsMenuBar>
|
|
174
220
|
<SandboxPermissionsAggregator
|
|
175
221
|
hooks={previewSandboxHooks}
|
|
@@ -178,37 +224,31 @@ export function Compositions({
|
|
|
178
224
|
/>
|
|
179
225
|
<div className={styles.previewArea}>
|
|
180
226
|
{isDraggingTray && <div className={styles.dragOverlay} />}
|
|
181
|
-
<
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
227
|
+
<PreviewPropsAggregator hooks={previewPropsHooks} component={component}>
|
|
228
|
+
{(previewAttrs) => (
|
|
229
|
+
<CompositionContent
|
|
230
|
+
{...previewAttrs}
|
|
231
|
+
className={styles.compositionPanel}
|
|
232
|
+
emptyState={emptyState}
|
|
233
|
+
component={component}
|
|
234
|
+
selected={currentComposition}
|
|
235
|
+
queryParams={queryParams}
|
|
236
|
+
sandbox={sandboxValue}
|
|
237
|
+
/>
|
|
238
|
+
)}
|
|
239
|
+
</PreviewPropsAggregator>
|
|
189
240
|
{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>
|
|
241
|
+
<LiveControlsTray
|
|
242
|
+
trayRef={trayRef}
|
|
243
|
+
collapsed={isTrayCollapsed}
|
|
244
|
+
height={trayHeight}
|
|
245
|
+
ready={ready}
|
|
246
|
+
defs={defs}
|
|
247
|
+
values={values}
|
|
248
|
+
onChange={onChange}
|
|
249
|
+
onResizeStripMouseDown={onResizeStripMouseDown}
|
|
250
|
+
onToggleExpanded={toggleTrayCollapsed}
|
|
251
|
+
/>
|
|
212
252
|
)}
|
|
213
253
|
</div>
|
|
214
254
|
</Pane>
|
|
@@ -234,6 +274,9 @@ export function Compositions({
|
|
|
234
274
|
isScaling={isScaling}
|
|
235
275
|
useNameParam={useNameParam}
|
|
236
276
|
includesEnvTemplate={component.preview?.includesEnvTemplate}
|
|
277
|
+
hasLiveControls={currentCompositionHasControls}
|
|
278
|
+
liveControlsActive={currentCompositionHasControls && !isTrayCollapsedForCurrent}
|
|
279
|
+
onToggleLiveControls={toggleTrayCollapsed}
|
|
237
280
|
onSelectComposition={(composition) => {
|
|
238
281
|
if (!currentComposition || !location) return;
|
|
239
282
|
const selectedCompositionFromUrl = params['*'];
|
|
@@ -252,9 +295,6 @@ export function Compositions({
|
|
|
252
295
|
}
|
|
253
296
|
const newPath = pathSegments.join('/');
|
|
254
297
|
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
298
|
}}
|
|
259
299
|
url={compositionUrl}
|
|
260
300
|
compositions={component.compositions}
|
|
@@ -272,6 +312,89 @@ export function Compositions({
|
|
|
272
312
|
);
|
|
273
313
|
}
|
|
274
314
|
|
|
315
|
+
type LiveControlsTrayProps = {
|
|
316
|
+
trayRef: React.RefObject<HTMLDivElement | null>;
|
|
317
|
+
collapsed: boolean;
|
|
318
|
+
height: number | null;
|
|
319
|
+
ready: boolean;
|
|
320
|
+
defs: UseLiveControlsResult['defs'];
|
|
321
|
+
values: UseLiveControlsResult['values'];
|
|
322
|
+
onChange: UseLiveControlsResult['onChange'];
|
|
323
|
+
onResizeStripMouseDown: (e: React.MouseEvent) => void;
|
|
324
|
+
onToggleExpanded: () => void;
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
function LiveControlsTray({
|
|
328
|
+
trayRef,
|
|
329
|
+
collapsed,
|
|
330
|
+
height,
|
|
331
|
+
ready,
|
|
332
|
+
defs,
|
|
333
|
+
values,
|
|
334
|
+
onChange,
|
|
335
|
+
onResizeStripMouseDown,
|
|
336
|
+
onToggleExpanded,
|
|
337
|
+
}: LiveControlsTrayProps) {
|
|
338
|
+
// height = null → let CSS size the tray to its content (with the max-height
|
|
339
|
+
// clamp doing the upper bound); height = number → user has drag-resized,
|
|
340
|
+
// pin to that.
|
|
341
|
+
const trayStyle = collapsed || height === null ? undefined : { height };
|
|
342
|
+
return (
|
|
343
|
+
<div
|
|
344
|
+
ref={trayRef}
|
|
345
|
+
className={classNames(styles.controlsTray, collapsed && styles.controlsTrayCollapsed)}
|
|
346
|
+
style={trayStyle}
|
|
347
|
+
>
|
|
348
|
+
{!collapsed && (
|
|
349
|
+
<div
|
|
350
|
+
className={styles.trayResizeStrip}
|
|
351
|
+
onMouseDown={onResizeStripMouseDown}
|
|
352
|
+
role="separator"
|
|
353
|
+
aria-orientation="horizontal"
|
|
354
|
+
aria-label="Resize live controls"
|
|
355
|
+
title="Drag to resize"
|
|
356
|
+
>
|
|
357
|
+
<div className={styles.trayDragBar} />
|
|
358
|
+
</div>
|
|
359
|
+
)}
|
|
360
|
+
<div
|
|
361
|
+
className={styles.trayHeaderInner}
|
|
362
|
+
onClick={onToggleExpanded}
|
|
363
|
+
role="button"
|
|
364
|
+
tabIndex={0}
|
|
365
|
+
onKeyDown={(e) => {
|
|
366
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
367
|
+
e.preventDefault();
|
|
368
|
+
onToggleExpanded();
|
|
369
|
+
}
|
|
370
|
+
}}
|
|
371
|
+
title={collapsed ? 'Click to expand' : 'Click to collapse'}
|
|
372
|
+
>
|
|
373
|
+
<div className={styles.trayTitleRow}>
|
|
374
|
+
<Icon of="settings" className={styles.trayIcon} />
|
|
375
|
+
<span className={styles.trayTitle}>Live Controls</span>
|
|
376
|
+
{ready && defs.length > 0 && <span className={styles.trayBadge}>{defs.length}</span>}
|
|
377
|
+
</div>
|
|
378
|
+
<span
|
|
379
|
+
className={classNames(styles.trayCollapseIcon, collapsed && styles.trayCollapseIconCollapsed)}
|
|
380
|
+
aria-hidden
|
|
381
|
+
>
|
|
382
|
+
<Icon of="right-rounded-corners" />
|
|
383
|
+
</span>
|
|
384
|
+
</div>
|
|
385
|
+
{!collapsed && (
|
|
386
|
+
<div className={styles.trayBody}>
|
|
387
|
+
{ready ? (
|
|
388
|
+
<LiveControls defs={defs} values={values} onChange={onChange} />
|
|
389
|
+
) : (
|
|
390
|
+
<div className={styles.trayEmpty}>No live controls available for this composition</div>
|
|
391
|
+
)}
|
|
392
|
+
</div>
|
|
393
|
+
)}
|
|
394
|
+
</div>
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
|
|
275
398
|
export type CompositionContentProps = {
|
|
276
399
|
component: ComponentModel;
|
|
277
400
|
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":[]}
|