@teambit/compositions 1.0.957 → 1.0.958

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.
@@ -1,5 +1,4 @@
1
1
  @import '@teambit/ui-foundation.ui.constants.z-indexes/z-indexes.module.scss';
2
- $panelBg: #fafafa;
3
2
 
4
3
  .compositionsPage {
5
4
  height: 100%;
@@ -13,27 +12,250 @@ $panelBg: #fafafa;
13
12
 
14
13
  .left {
15
14
  min-width: 200px;
16
-
17
- //cannot assign max-width directly to right side
18
15
  max-width: calc(100% - 200px);
19
-
20
16
  display: flex;
21
- overflow-y: auto;
17
+ overflow: hidden;
22
18
  flex-direction: column;
19
+ background: var(--bit-bg-color, #ffffff);
23
20
  }
24
21
 
25
22
  .right {
26
- background: var(--background-color, $panelBg);
23
+ background: var(--compositions-panel-bg, #f7f7f8);
27
24
  overflow-y: auto;
28
- // this is to fix the right panel when it gets too big or too small
29
25
  min-width: 200px;
30
26
  max-width: calc(100% - 200px);
27
+ border-left: 1px solid var(--bit-border-color-lightest, #eaeaec);
31
28
  }
32
29
 
33
30
  .menuBar {
34
- border-bottom: 1px solid var(--bit-border-color-lightest, #ededed);
31
+ border-bottom: 1px solid var(--bit-border-color-lightest, #eaeaec);
32
+ background: var(--bit-bg-color, #ffffff);
33
+ flex-shrink: 0;
34
+
35
+ > div {
36
+ display: flex;
37
+ align-items: center;
38
+ gap: 2px;
39
+ }
40
+
41
+ // normalize all OptionButton instances to match our toolbar style
42
+ :global([class*='optionButton']) {
43
+ width: 30px;
44
+ height: 30px;
45
+ padding: 0;
46
+ border-radius: 6px;
47
+ font-size: 14px;
48
+ display: inline-flex;
49
+ align-items: center;
50
+ justify-content: center;
51
+ background: transparent;
52
+ color: var(--bit-text-color-light, #8b8d98);
53
+ border: none;
54
+ cursor: pointer;
55
+ transition: all 140ms ease;
56
+
57
+ &:hover {
58
+ background: var(--surface-hover-color, #f0f0f2) !important;
59
+ color: var(--bit-text-color-heavy, #1c2024);
60
+ }
61
+
62
+ &:active {
63
+ transform: scale(0.95);
64
+ }
65
+
66
+ &:global(.active),
67
+ &[class*='active'] {
68
+ background: rgba(108, 92, 231, 0.1) !important;
69
+ color: var(--bit-accent-color, #6c5ce7) !important;
70
+ }
71
+ }
35
72
  }
36
73
 
74
+ // ─── Toolbar ────────────────────────────────────────────────────────────
75
+ .toolbar {
76
+ display: flex;
77
+ align-items: center;
78
+ gap: 2px;
79
+ padding: 0 4px;
80
+ }
81
+
82
+ .toolbarButton {
83
+ display: flex;
84
+ align-items: center;
85
+ justify-content: center;
86
+ width: 30px;
87
+ height: 30px;
88
+ border-radius: 6px;
89
+ border: none;
90
+ background: transparent;
91
+ color: var(--bit-text-color-light, #8b8d98);
92
+ cursor: pointer;
93
+ transition: all 140ms ease;
94
+ text-decoration: none;
95
+ font-size: 14px;
96
+ padding: 0;
97
+
98
+ &:hover {
99
+ background: var(--surface-hover-color, #f0f0f2);
100
+ color: var(--bit-text-color-heavy, #1c2024);
101
+ }
102
+
103
+ &:active {
104
+ background: var(--bit-border-color-lightest, #eaeaec);
105
+ transform: scale(0.95);
106
+ }
107
+ }
108
+
109
+ .toolbarButtonActive {
110
+ background: rgba(108, 92, 231, 0.1);
111
+ color: var(--bit-accent-color, #6c5ce7);
112
+
113
+ &:hover {
114
+ background: rgba(108, 92, 231, 0.15);
115
+ color: var(--bit-accent-color, #6c5ce7);
116
+ }
117
+ }
118
+
119
+ // ─── Preview + Controls Tray area ───────────────────────────────────────
120
+ .previewArea {
121
+ position: relative;
122
+ display: flex;
123
+ flex-direction: column;
124
+ flex: 1;
125
+ min-height: 0;
126
+ overflow: hidden;
127
+ }
128
+
129
+ .compositionPanel {
130
+ display: flex;
131
+ flex: 1;
132
+ min-height: 0;
133
+ }
134
+
135
+ .dragOverlay {
136
+ position: absolute;
137
+ inset: 0;
138
+ z-index: 10;
139
+ cursor: ns-resize;
140
+ }
141
+
142
+ // ─── Controls Tray (bottom panel, like DevTools) ────────────────────────
143
+ .controlsTray {
144
+ flex-shrink: 0;
145
+ display: flex;
146
+ flex-direction: column;
147
+ border-top: 1px solid var(--bit-border-color-lightest, #eaeaec);
148
+ background: var(--compositions-panel-bg, #f7f7f8);
149
+ overflow: hidden;
150
+ }
151
+
152
+ .trayDragHandle {
153
+ flex-shrink: 0;
154
+ height: 8px;
155
+ display: flex;
156
+ align-items: center;
157
+ justify-content: center;
158
+ cursor: ns-resize;
159
+ background: transparent;
160
+ transition: background 120ms ease;
161
+
162
+ &:hover {
163
+ background: rgba(0, 0, 0, 0.03);
164
+ }
165
+
166
+ &:hover .trayDragBar {
167
+ background: var(--bit-text-color-light, #8b8d98);
168
+ width: 48px;
169
+ }
170
+ }
171
+
172
+ .trayDragBar {
173
+ width: 32px;
174
+ height: 3px;
175
+ border-radius: 2px;
176
+ background: var(--bit-border-color-lightest, #d0d0d3);
177
+ transition: all 140ms ease;
178
+ }
179
+
180
+ .trayHeader {
181
+ flex-shrink: 0;
182
+ display: flex;
183
+ align-items: center;
184
+ justify-content: space-between;
185
+ padding: 4px 14px 8px;
186
+ }
187
+
188
+ .trayTitleRow {
189
+ display: flex;
190
+ align-items: center;
191
+ gap: 8px;
192
+ }
193
+
194
+ .trayIcon {
195
+ font-size: 13px;
196
+ color: var(--bit-accent-color, #6c5ce7);
197
+ }
198
+
199
+ .trayTitle {
200
+ font-size: 11px;
201
+ font-weight: 600;
202
+ letter-spacing: 0.5px;
203
+ text-transform: uppercase;
204
+ color: var(--bit-text-color-light, #8b8d98);
205
+ }
206
+
207
+ .trayBadge {
208
+ display: inline-flex;
209
+ align-items: center;
210
+ justify-content: center;
211
+ min-width: 18px;
212
+ height: 18px;
213
+ padding: 0 5px;
214
+ border-radius: 9px;
215
+ font-size: 10px;
216
+ font-weight: 600;
217
+ background: var(--bit-accent-color, #6c5ce7);
218
+ color: #ffffff;
219
+ line-height: 1;
220
+ }
221
+
222
+ .trayClose {
223
+ display: flex;
224
+ align-items: center;
225
+ justify-content: center;
226
+ width: 24px;
227
+ height: 24px;
228
+ border-radius: 4px;
229
+ border: none;
230
+ background: transparent;
231
+ color: var(--bit-text-color-light, #8b8d98);
232
+ cursor: pointer;
233
+ font-size: 12px;
234
+ padding: 0;
235
+ transition: all 120ms ease;
236
+
237
+ &:hover {
238
+ background: rgba(0, 0, 0, 0.06);
239
+ color: var(--bit-text-color-heavy, #1c2024);
240
+ }
241
+ }
242
+
243
+ .trayBody {
244
+ flex: 1;
245
+ overflow-y: auto;
246
+ overflow-x: hidden;
247
+ }
248
+
249
+ .trayEmpty {
250
+ display: flex;
251
+ align-items: center;
252
+ justify-content: center;
253
+ padding: 24px 16px;
254
+ font-size: 12px;
255
+ color: var(--bit-text-color-light, #8b8d98);
256
+ }
257
+
258
+ // ─── Splitter ───────────────────────────────────────────────────────────
37
259
  .splitter {
38
260
  position: relative;
39
261
 
@@ -47,6 +269,7 @@ $panelBg: #fafafa;
47
269
  right: 0;
48
270
  }
49
271
 
272
+ // ─── Tabs ───────────────────────────────────────────────────────────────
50
273
  .tabsContainer {
51
274
  display: flex;
52
275
  flex-direction: column;
@@ -78,6 +301,7 @@ $panelBg: #fafafa;
78
301
  }
79
302
  }
80
303
 
304
+ // ─── Empty / error states ───────────────────────────────────────────────
81
305
  .noCompositionsPage {
82
306
  padding: 50px 40px 100px 40px;
83
307
  width: 100%;
@@ -97,8 +321,3 @@ $panelBg: #fafafa;
97
321
  .buildStatusMessage {
98
322
  margin: auto;
99
323
  }
100
-
101
- .compositionPanel {
102
- display: flex;
103
- flex: 1;
104
- }
package/compositions.tsx CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { ReactNode } from 'react';
2
- import React, { useContext, useEffect, useState, useMemo, useRef } from 'react';
2
+ import React, { useContext, useEffect, useState, useMemo, useRef, useCallback } from 'react';
3
3
  import { useParams, useSearchParams } from 'react-router-dom';
4
4
  import head from 'lodash.head';
5
5
  import queryString from 'query-string';
@@ -25,11 +25,14 @@ import { Link as BaseLink, useNavigate, useLocation } from '@teambit/base-react.
25
25
  import { OptionButton } from '@teambit/design.ui.input.option-button';
26
26
  import { StatusMessageCard } from '@teambit/design.ui.surfaces.status-message-card';
27
27
  import { Tooltip } from '@teambit/design.ui.tooltip';
28
+ import { Icon } from '@teambit/evangelist.elements.icon';
29
+ import { useLiveControls } from '@teambit/compositions.ui.composition-live-controls';
28
30
  import type { EmptyStateSlot, CompositionsMenuSlot, UsePreviewSandboxSlot } from './compositions.ui.runtime';
29
31
  import type { Composition } from './composition';
30
32
  import styles from './compositions.module.scss';
31
33
  import { ComponentComposition } from './ui';
32
34
  import { CompositionsPanel } from './ui/compositions-panel/compositions-panel';
35
+ import { LiveControls } from './ui/compositions-panel/live-control-panel';
33
36
  import type { ComponentCompositionProps } from './ui/composition-preview';
34
37
  import { useDefaultControlsSchemaResponder } from './use-default-controls-schema-responder';
35
38
 
@@ -94,6 +97,37 @@ export function Compositions({
94
97
 
95
98
  const queryParams = useMemo(() => queryString.stringify(compositionParams), [compositionParams]);
96
99
 
100
+ const [controlsTrayOpen, setControlsTrayOpen] = useState(true);
101
+ const [isDraggingTray, setIsDraggingTray] = useState(false);
102
+ const trayRef = useRef<HTMLDivElement>(null);
103
+ const trayHeightRef = useRef(260);
104
+ const { ready, defs, values, onChange } = useLiveControls();
105
+
106
+ const onTrayDragStart = useCallback((e: React.MouseEvent) => {
107
+ e.preventDefault();
108
+ const startY = e.clientY;
109
+ const startHeight = trayHeightRef.current;
110
+ setIsDraggingTray(true);
111
+ document.body.style.cursor = 'ns-resize';
112
+ document.body.style.userSelect = 'none';
113
+ const onMove = (ev: MouseEvent) => {
114
+ const next = Math.max(120, Math.min(600, startHeight + (startY - ev.clientY)));
115
+ trayHeightRef.current = next;
116
+ if (trayRef.current) {
117
+ trayRef.current.style.height = `${next}px`;
118
+ }
119
+ };
120
+ const onUp = () => {
121
+ setIsDraggingTray(false);
122
+ document.body.style.cursor = '';
123
+ document.body.style.userSelect = '';
124
+ document.removeEventListener('mousemove', onMove);
125
+ document.removeEventListener('mouseup', onUp);
126
+ };
127
+ document.addEventListener('mousemove', onMove);
128
+ document.addEventListener('mouseup', onUp);
129
+ }, []);
130
+
97
131
  // collapse sidebar when empty, reopen when not
98
132
  useEffect(() => setSidebarOpenness(showSidebar), [showSidebar]);
99
133
  useEffect(() => {
@@ -113,30 +147,70 @@ export function Compositions({
113
147
  });
114
148
  }, [enableLiveControls]);
115
149
 
150
+ const currentCompositionHasControls = ready && defs.length > 0;
151
+ const showControlsTray = currentCompositionHasControls && controlsTrayOpen;
152
+
116
153
  return (
117
154
  <CompositionContextProvider queryParams={compositionParams} setQueryParams={setCompositionParams}>
118
155
  <SplitPane layout={sidebarOpenness} size="80%" className={styles.compositionsPage}>
119
156
  <Pane className={styles.left}>
120
157
  <CompositionsMenuBar menuBarWidgets={menuBarWidgets} className={styles.menuBar}>
121
- <Tooltip content={'Open in new tab'} placement="right">
122
- <Link external href={currentCompositionFullUrl} className={styles.openInNewTab}>
158
+ <Tooltip content={'Open in new tab'} placement="bottom">
159
+ <Link external href={currentCompositionFullUrl} className={styles.toolbarButton}>
123
160
  <OptionButton icon="open-tab" />
124
161
  </Link>
125
162
  </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
+ )}
126
173
  </CompositionsMenuBar>
127
174
  <SandboxPermissionsAggregator
128
175
  hooks={previewSandboxHooks}
129
176
  onSandboxChange={setSandboxValue}
130
177
  component={component}
131
178
  />
132
- <CompositionContent
133
- className={styles.compositionPanel}
134
- emptyState={emptyState}
135
- component={component}
136
- selected={currentComposition}
137
- queryParams={queryParams}
138
- sandbox={sandboxValue}
139
- />
179
+ <div className={styles.previewArea}>
180
+ {isDraggingTray && <div className={styles.dragOverlay} />}
181
+ <CompositionContent
182
+ className={styles.compositionPanel}
183
+ emptyState={emptyState}
184
+ component={component}
185
+ selected={currentComposition}
186
+ queryParams={queryParams}
187
+ sandbox={sandboxValue}
188
+ />
189
+ {showControlsTray && (
190
+ <div ref={trayRef} className={styles.controlsTray} style={{ height: trayHeightRef.current }}>
191
+ <div className={styles.trayDragHandle} onMouseDown={onTrayDragStart}>
192
+ <div className={styles.trayDragBar} />
193
+ </div>
194
+ <div className={styles.trayHeader}>
195
+ <div className={styles.trayTitleRow}>
196
+ <Icon of="settings" className={styles.trayIcon} />
197
+ <span className={styles.trayTitle}>Live Controls</span>
198
+ {ready && defs.length > 0 && <span className={styles.trayBadge}>{defs.length}</span>}
199
+ </div>
200
+ <button className={styles.trayClose} onClick={() => setControlsTrayOpen(false)}>
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>
212
+ )}
213
+ </div>
140
214
  </Pane>
141
215
  <HoverSplitter className={styles.splitter}>
142
216
  <Collapser
@@ -180,6 +180,20 @@ function _designUi4() {
180
180
  };
181
181
  return data;
182
182
  }
183
+ function _evangelistElements() {
184
+ const data = require("@teambit/evangelist.elements.icon");
185
+ _evangelistElements = function () {
186
+ return data;
187
+ };
188
+ return data;
189
+ }
190
+ function _compositionsUi2() {
191
+ const data = require("@teambit/compositions.ui.composition-live-controls");
192
+ _compositionsUi2 = function () {
193
+ return data;
194
+ };
195
+ return data;
196
+ }
183
197
  function _compositionsModule() {
184
198
  const data = _interopRequireDefault(require("./compositions.module.scss"));
185
199
  _compositionsModule = function () {
@@ -201,6 +215,13 @@ function _compositionsPanel() {
201
215
  };
202
216
  return data;
203
217
  }
218
+ function _liveControlPanel() {
219
+ const data = require("./ui/compositions-panel/live-control-panel");
220
+ _liveControlPanel = function () {
221
+ return data;
222
+ };
223
+ return data;
224
+ }
204
225
  function _useDefaultControlsSchemaResponder() {
205
226
  const data = require("./use-default-controls-schema-responder");
206
227
  _useDefaultControlsSchemaResponder = function () {
@@ -259,6 +280,40 @@ function Compositions({
259
280
  fullscreen: true
260
281
  });
261
282
  const queryParams = (0, _react().useMemo)(() => _queryString().default.stringify(compositionParams), [compositionParams]);
283
+ const [controlsTrayOpen, setControlsTrayOpen] = (0, _react().useState)(true);
284
+ const [isDraggingTray, setIsDraggingTray] = (0, _react().useState)(false);
285
+ const trayRef = (0, _react().useRef)(null);
286
+ const trayHeightRef = (0, _react().useRef)(260);
287
+ const {
288
+ ready,
289
+ defs,
290
+ values,
291
+ onChange
292
+ } = (0, _compositionsUi2().useLiveControls)();
293
+ const onTrayDragStart = (0, _react().useCallback)(e => {
294
+ e.preventDefault();
295
+ const startY = e.clientY;
296
+ const startHeight = trayHeightRef.current;
297
+ setIsDraggingTray(true);
298
+ document.body.style.cursor = 'ns-resize';
299
+ document.body.style.userSelect = 'none';
300
+ const onMove = ev => {
301
+ const next = Math.max(120, Math.min(600, startHeight + (startY - ev.clientY)));
302
+ trayHeightRef.current = next;
303
+ if (trayRef.current) {
304
+ trayRef.current.style.height = `${next}px`;
305
+ }
306
+ };
307
+ const onUp = () => {
308
+ setIsDraggingTray(false);
309
+ document.body.style.cursor = '';
310
+ document.body.style.userSelect = '';
311
+ document.removeEventListener('mousemove', onMove);
312
+ document.removeEventListener('mouseup', onUp);
313
+ };
314
+ document.addEventListener('mousemove', onMove);
315
+ document.addEventListener('mouseup', onUp);
316
+ }, []);
262
317
 
263
318
  // collapse sidebar when empty, reopen when not
264
319
  (0, _react().useEffect)(() => setSidebarOpenness(showSidebar), [showSidebar]);
@@ -279,6 +334,8 @@ function Compositions({
279
334
  return next;
280
335
  });
281
336
  }, [enableLiveControls]);
337
+ const currentCompositionHasControls = ready && defs.length > 0;
338
+ const showControlsTray = currentCompositionHasControls && controlsTrayOpen;
282
339
  return /*#__PURE__*/_react().default.createElement(_compositionsUiHooks().CompositionContextProvider, {
283
340
  queryParams: compositionParams,
284
341
  setQueryParams: setCompositionParams
@@ -293,17 +350,29 @@ function Compositions({
293
350
  className: _compositionsModule().default.menuBar
294
351
  }, /*#__PURE__*/_react().default.createElement(_designUi4().Tooltip, {
295
352
  content: 'Open in new tab',
296
- placement: "right"
353
+ placement: "bottom"
297
354
  }, /*#__PURE__*/_react().default.createElement(Link, {
298
355
  external: true,
299
356
  href: currentCompositionFullUrl,
300
- className: _compositionsModule().default.openInNewTab
357
+ className: _compositionsModule().default.toolbarButton
301
358
  }, /*#__PURE__*/_react().default.createElement(_designUiInput().OptionButton, {
302
359
  icon: "open-tab"
360
+ }))), currentCompositionHasControls && /*#__PURE__*/_react().default.createElement(_designUi4().Tooltip, {
361
+ content: controlsTrayOpen ? 'Hide controls' : 'Show controls',
362
+ placement: "bottom"
363
+ }, /*#__PURE__*/_react().default.createElement("button", {
364
+ className: `${_compositionsModule().default.toolbarButton} ${controlsTrayOpen ? _compositionsModule().default.toolbarButtonActive : ''}`,
365
+ onClick: () => setControlsTrayOpen(o => !o)
366
+ }, /*#__PURE__*/_react().default.createElement(_evangelistElements().Icon, {
367
+ of: "settings"
303
368
  })))), /*#__PURE__*/_react().default.createElement(_previewUi().SandboxPermissionsAggregator, {
304
369
  hooks: previewSandboxHooks,
305
370
  onSandboxChange: setSandboxValue,
306
371
  component: component
372
+ }), /*#__PURE__*/_react().default.createElement("div", {
373
+ className: _compositionsModule().default.previewArea
374
+ }, isDraggingTray && /*#__PURE__*/_react().default.createElement("div", {
375
+ className: _compositionsModule().default.dragOverlay
307
376
  }), /*#__PURE__*/_react().default.createElement(CompositionContent, {
308
377
  className: _compositionsModule().default.compositionPanel,
309
378
  emptyState: emptyState,
@@ -311,7 +380,42 @@ function Compositions({
311
380
  selected: currentComposition,
312
381
  queryParams: queryParams,
313
382
  sandbox: sandboxValue
314
- })), /*#__PURE__*/_react().default.createElement(_baseUiSurfacesSplitPane2().HoverSplitter, {
383
+ }), showControlsTray && /*#__PURE__*/_react().default.createElement("div", {
384
+ ref: trayRef,
385
+ className: _compositionsModule().default.controlsTray,
386
+ style: {
387
+ height: trayHeightRef.current
388
+ }
389
+ }, /*#__PURE__*/_react().default.createElement("div", {
390
+ className: _compositionsModule().default.trayDragHandle,
391
+ onMouseDown: onTrayDragStart
392
+ }, /*#__PURE__*/_react().default.createElement("div", {
393
+ className: _compositionsModule().default.trayDragBar
394
+ })), /*#__PURE__*/_react().default.createElement("div", {
395
+ className: _compositionsModule().default.trayHeader
396
+ }, /*#__PURE__*/_react().default.createElement("div", {
397
+ className: _compositionsModule().default.trayTitleRow
398
+ }, /*#__PURE__*/_react().default.createElement(_evangelistElements().Icon, {
399
+ of: "settings",
400
+ className: _compositionsModule().default.trayIcon
401
+ }), /*#__PURE__*/_react().default.createElement("span", {
402
+ className: _compositionsModule().default.trayTitle
403
+ }, "Live Controls"), ready && defs.length > 0 && /*#__PURE__*/_react().default.createElement("span", {
404
+ className: _compositionsModule().default.trayBadge
405
+ }, defs.length)), /*#__PURE__*/_react().default.createElement("button", {
406
+ className: _compositionsModule().default.trayClose,
407
+ onClick: () => setControlsTrayOpen(false)
408
+ }, /*#__PURE__*/_react().default.createElement(_evangelistElements().Icon, {
409
+ of: "x"
410
+ }))), /*#__PURE__*/_react().default.createElement("div", {
411
+ className: _compositionsModule().default.trayBody
412
+ }, ready ? /*#__PURE__*/_react().default.createElement(_liveControlPanel().LiveControls, {
413
+ defs: defs,
414
+ values: values,
415
+ onChange: onChange
416
+ }) : /*#__PURE__*/_react().default.createElement("div", {
417
+ className: _compositionsModule().default.trayEmpty
418
+ }, "No live controls available for this composition"))))), /*#__PURE__*/_react().default.createElement(_baseUiSurfacesSplitPane2().HoverSplitter, {
315
419
  className: _compositionsModule().default.splitter
316
420
  }, /*#__PURE__*/_react().default.createElement(_uiFoundationUiButtons().Collapser, {
317
421
  placement: "left",