@teambit/component.ui.version-dropdown 0.0.785 → 0.0.787

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.
Files changed (33) hide show
  1. package/dist/index.d.ts +3 -1
  2. package/dist/index.js +1 -0
  3. package/dist/index.js.map +1 -1
  4. package/dist/{preview-1686886045563.js → preview-1687362266169.js} +2 -2
  5. package/dist/version-dropdown-placeholder.d.ts +11 -5
  6. package/dist/version-dropdown-placeholder.js +49 -32
  7. package/dist/version-dropdown-placeholder.js.map +1 -1
  8. package/dist/version-dropdown-placeholder.module.scss +21 -71
  9. package/dist/version-dropdown.composition.js +6 -2
  10. package/dist/version-dropdown.composition.js.map +1 -1
  11. package/dist/version-dropdown.d.ts +36 -8
  12. package/dist/version-dropdown.docs.d.ts +3 -4
  13. package/dist/version-dropdown.js +73 -30
  14. package/dist/version-dropdown.js.map +1 -1
  15. package/dist/version-dropdown.module.scss +17 -2
  16. package/dist/version-dropdown.spec.js +9 -9
  17. package/dist/version-dropdown.spec.js.map +1 -1
  18. package/dist/version-info/version-info.d.ts +9 -2
  19. package/dist/version-info/version-info.js +17 -8
  20. package/dist/version-info/version-info.js.map +1 -1
  21. package/dist/version-info/version-info.module.scss +16 -10
  22. package/index.ts +3 -1
  23. package/package-tar/teambit-component.ui.version-dropdown-0.0.787.tgz +0 -0
  24. package/package.json +7 -5
  25. package/version-dropdown-placeholder.module.scss +21 -71
  26. package/version-dropdown-placeholder.tsx +77 -54
  27. package/version-dropdown.composition.tsx +13 -2
  28. package/version-dropdown.module.scss +17 -2
  29. package/version-dropdown.spec.tsx +10 -9
  30. package/version-dropdown.tsx +207 -94
  31. package/version-info/version-info.module.scss +16 -10
  32. package/version-info/version-info.tsx +35 -21
  33. package/package-tar/teambit-component.ui.version-dropdown-0.0.785.tgz +0 -0
@@ -1,4 +1,4 @@
1
- import React, { useState, ReactNode } from 'react';
1
+ import React, { useState } from 'react';
2
2
  import { MenuLinkItem } from '@teambit/design.ui.surfaces.menu.link-item';
3
3
  import { Dropdown } from '@teambit/evangelist.surfaces.dropdown';
4
4
  import { Tab } from '@teambit/ui-foundation.ui.use-box.tab';
@@ -7,67 +7,123 @@ import { UserAvatar } from '@teambit/design.ui.avatar';
7
7
  import { LineSkeleton } from '@teambit/base-ui.loaders.skeleton';
8
8
  import { LaneModel } from '@teambit/lanes.ui.models.lanes-model';
9
9
  import classNames from 'classnames';
10
-
11
10
  import styles from './version-dropdown.module.scss';
12
11
  import { VersionInfo } from './version-info';
13
12
  import { LaneInfo } from './lane-info';
14
- import { SimpleVersion } from './version-dropdown-placeholder';
13
+ import { SimpleVersion, VersionProps } from './version-dropdown-placeholder';
15
14
 
16
15
  export const LOCAL_VERSION = 'workspace';
17
16
 
18
17
  export type DropdownComponentVersion = Partial<LegacyComponentLog> & { version: string };
19
18
 
20
- export type VersionDropdownProps = {
21
- tags: DropdownComponentVersion[];
19
+ export type UseComponentDropdownVersionsResult = {
20
+ tags?: DropdownComponentVersion[];
22
21
  snaps?: DropdownComponentVersion[];
23
- lanes?: LaneModel[];
22
+ loading?: boolean;
23
+ };
24
+ export type UseComponentDropdownVersionsProps = {
25
+ skip?: boolean;
26
+ };
27
+ export type UseComponentDropdownVersions = (
28
+ props?: UseComponentDropdownVersionsProps
29
+ ) => UseComponentDropdownVersionsResult;
30
+ export type GetActiveTabIndex = (
31
+ currentVersion?: string,
32
+ tabs?: Array<VersionMenuTab>,
33
+ tags?: DropdownComponentVersion[],
34
+ snaps?: DropdownComponentVersion[],
35
+ currentLane?: LaneModel
36
+ ) => number;
37
+ export type VersionDropdownProps = {
24
38
  localVersion?: boolean;
25
- currentVersion: string;
26
- currentLane?: LaneModel;
27
39
  latestVersion?: string;
40
+ currentVersion: string;
41
+ useCurrentVersionLog?: (props?: { skip?: boolean; version?: string }) => DropdownComponentVersion | undefined;
42
+ hasMoreVersions?: boolean;
28
43
  loading?: boolean;
44
+ useComponentVersions?: UseComponentDropdownVersions;
45
+ currentLane?: LaneModel;
46
+ lanes?: LaneModel[];
47
+ getActiveTabIndex?: GetActiveTabIndex;
29
48
  overrideVersionHref?: (version: string) => string;
30
49
  placeholderClassName?: string;
31
50
  dropdownClassName?: string;
32
51
  menuClassName?: string;
33
52
  showVersionDetails?: boolean;
34
53
  disabled?: boolean;
35
- placeholderComponent?: ReactNode;
54
+ PlaceholderComponent?: React.ComponentType<VersionProps>;
36
55
  } & React.HTMLAttributes<HTMLDivElement>;
37
56
 
38
- export function VersionDropdown({
39
- snaps,
40
- tags,
41
- lanes,
57
+ export const VersionDropdown = React.memo(_VersionDropdown);
58
+ const VersionMenu = React.memo(_VersionMenu);
59
+ function _VersionDropdown({
42
60
  currentVersion,
43
61
  latestVersion,
44
62
  localVersion,
63
+ useCurrentVersionLog,
64
+ hasMoreVersions,
45
65
  loading,
46
- currentLane,
47
66
  overrideVersionHref,
48
67
  className,
49
68
  placeholderClassName,
69
+ getActiveTabIndex,
50
70
  dropdownClassName,
51
71
  menuClassName,
52
- showVersionDetails,
72
+ showVersionDetails = true,
53
73
  disabled,
54
- placeholderComponent = (
74
+ PlaceholderComponent: _PlaceholderComponent,
75
+ currentLane,
76
+ useComponentVersions,
77
+ lanes,
78
+ ...rest
79
+ }: VersionDropdownProps) {
80
+ const [key, setKey] = useState(0);
81
+ const singleVersion = !hasMoreVersions;
82
+ const [open, setOpen] = useState(false);
83
+
84
+ React.useEffect(() => {
85
+ if (loading && open) {
86
+ setOpen(false);
87
+ }
88
+ }, [loading]);
89
+
90
+ const handlePlaceholderClicked = (e: React.MouseEvent<HTMLDivElement>) => {
91
+ if (loading) return;
92
+ if (e.target === e.currentTarget) {
93
+ setOpen((o) => !o);
94
+ }
95
+ };
96
+
97
+ const defaultPlaceholder = (
55
98
  <SimpleVersion
99
+ useCurrentVersionLog={useCurrentVersionLog}
56
100
  disabled={disabled}
57
- snaps={snaps}
58
- tags={tags}
59
101
  className={placeholderClassName}
60
102
  currentVersion={currentVersion}
103
+ onClick={handlePlaceholderClicked}
104
+ hasMoreVersions={hasMoreVersions}
105
+ loading={loading}
106
+ showFullVersion={currentVersion === 'workspace'}
61
107
  />
62
- ),
63
- ...rest
64
- }: VersionDropdownProps) {
65
- const [key, setKey] = useState(0);
108
+ );
66
109
 
67
- const singleVersion = (snaps || []).concat(tags).length < 2 && !localVersion;
110
+ const PlaceholderComponent = _PlaceholderComponent ? (
111
+ <_PlaceholderComponent
112
+ useCurrentVersionLog={useCurrentVersionLog}
113
+ disabled={disabled}
114
+ className={placeholderClassName}
115
+ currentVersion={currentVersion}
116
+ onClick={handlePlaceholderClicked}
117
+ hasMoreVersions={hasMoreVersions}
118
+ loading={loading}
119
+ showFullVersion={currentVersion === 'workspace'}
120
+ />
121
+ ) : (
122
+ defaultPlaceholder
123
+ );
68
124
 
69
125
  if (disabled || (singleVersion && !loading)) {
70
- return <div className={classNames(styles.noVersions, className)}>{placeholderComponent}</div>;
126
+ return <div className={classNames(styles.noVersions, className)}>{PlaceholderComponent}</div>;
71
127
  }
72
128
 
73
129
  return (
@@ -75,94 +131,167 @@ export function VersionDropdown({
75
131
  <Dropdown
76
132
  className={classNames(styles.dropdown, dropdownClassName)}
77
133
  dropClass={classNames(styles.menu, menuClassName)}
78
- clickToggles={false}
79
- clickPlaceholderToggles={true}
80
- onChange={(_e, open) => open && setKey((x) => x + 1)} // to reset menu to initial state when toggling
134
+ open={open}
135
+ onClick={handlePlaceholderClicked}
136
+ onClickOutside={() => setOpen(false)}
137
+ onChange={(_e, _open) => _open && setKey((x) => x + 1)} // to reset menu to initial state when toggling
81
138
  PlaceholderComponent={({ children, ...other }) => (
82
- <div {...other} className={placeholderClassName}>
139
+ <div {...other} className={placeholderClassName} onClick={handlePlaceholderClicked}>
83
140
  {children}
84
141
  </div>
85
142
  )}
86
- placeholder={placeholderComponent}
143
+ placeholder={PlaceholderComponent}
87
144
  >
88
- {loading && <LineSkeleton className={styles.loading} count={6} />}
89
- {loading || (
90
- <VersionMenu
91
- className={menuClassName}
92
- key={key}
93
- tags={tags}
94
- snaps={snaps}
95
- lanes={lanes}
96
- currentVersion={currentVersion}
97
- latestVersion={latestVersion}
98
- localVersion={localVersion}
99
- currentLane={currentLane}
100
- overrideVersionHref={overrideVersionHref}
101
- showVersionDetails={showVersionDetails}
102
- />
103
- )}
145
+ <VersionMenu
146
+ className={menuClassName}
147
+ key={key}
148
+ currentVersion={currentVersion}
149
+ latestVersion={latestVersion}
150
+ localVersion={localVersion}
151
+ overrideVersionHref={overrideVersionHref}
152
+ showVersionDetails={showVersionDetails}
153
+ currentLane={currentLane}
154
+ getActiveTabIndex={getActiveTabIndex}
155
+ lanes={lanes}
156
+ useVersions={useComponentVersions}
157
+ open={open}
158
+ />
104
159
  </Dropdown>
105
160
  </div>
106
161
  );
107
162
  }
108
163
 
109
164
  type VersionMenuProps = {
110
- tags?: DropdownComponentVersion[];
111
- snaps?: DropdownComponentVersion[];
112
- lanes?: LaneModel[];
113
165
  localVersion?: boolean;
114
166
  currentVersion?: string;
115
167
  latestVersion?: string;
168
+ useVersions?: UseComponentDropdownVersions;
116
169
  currentLane?: LaneModel;
170
+ lanes?: LaneModel[];
117
171
  overrideVersionHref?: (version: string) => string;
118
172
  showVersionDetails?: boolean;
173
+ loading?: boolean;
174
+ getActiveTabIndex?: GetActiveTabIndex;
175
+ open?: boolean;
119
176
  } & React.HTMLAttributes<HTMLDivElement>;
120
177
 
121
- const VERSION_TAB_NAMES = ['TAG', 'SNAP', 'LANE'] as const;
178
+ export type VersionMenuTab =
179
+ | {
180
+ name: 'SNAP';
181
+ payload: DropdownComponentVersion[];
182
+ }
183
+ | {
184
+ name: 'LANE';
185
+ payload: LaneModel[];
186
+ }
187
+ | {
188
+ name: 'TAG';
189
+ payload: DropdownComponentVersion[];
190
+ };
122
191
 
123
- function VersionMenu({
124
- tags,
125
- snaps,
126
- lanes,
192
+ const defaultActiveTabIndex: GetActiveTabIndex = (currentVersion, tabs = [], tags, snaps) => {
193
+ if ((snaps || []).some((snap) => snap.version === currentVersion))
194
+ return tabs.findIndex((tab) => tab.name === 'SNAP');
195
+ return 0;
196
+ };
197
+
198
+ const VERSION_TAB_NAMES = ['TAG', 'SNAP', 'LANE'] as const;
199
+ function _VersionMenu({
127
200
  currentVersion,
128
201
  localVersion,
129
202
  latestVersion,
130
- currentLane,
131
203
  overrideVersionHref,
132
204
  showVersionDetails,
205
+ useVersions,
206
+ currentLane,
207
+ lanes,
208
+ getActiveTabIndex = defaultActiveTabIndex,
209
+ loading: loadingFromProps,
210
+ open,
133
211
  ...rest
134
212
  }: VersionMenuProps) {
135
- const tabs = VERSION_TAB_NAMES.map((name) => {
136
- switch (name) {
137
- case 'SNAP':
138
- return { name, payload: snaps || [] };
139
- case 'LANE':
140
- return { name, payload: lanes || [] };
141
- default:
142
- return { name, payload: tags || [] };
143
- }
144
- }).filter((tab) => tab.payload.length > 0);
145
-
146
- const getActiveTabIndex = () => {
147
- if (currentLane?.components.some((c) => c.version === currentVersion))
148
- return tabs.findIndex((tab) => tab.name === 'LANE');
149
- if ((snaps || []).some((snap) => snap.version === currentVersion))
150
- return tabs.findIndex((tab) => tab.name === 'SNAP');
151
- return 0;
152
- };
213
+ const { snaps, tags, loading: loadingVersions } = useVersions?.() || {};
214
+ const loading = loadingFromProps || loadingVersions;
215
+
216
+ const tabs = React.useMemo(
217
+ () =>
218
+ VERSION_TAB_NAMES.map((name) => {
219
+ switch (name) {
220
+ case 'SNAP':
221
+ return { name, payload: snaps || [] };
222
+ case 'LANE':
223
+ return { name, payload: lanes || [] };
224
+ default:
225
+ return { name, payload: tags || [] };
226
+ }
227
+ }).filter((tab) => tab.payload.length > 0),
228
+ [snaps?.length, tags?.length, lanes?.length, loading]
229
+ );
230
+
231
+ const [activeTabIndex, setActiveTab] = React.useState<number | undefined>(
232
+ getActiveTabIndex(currentVersion, tabs, tags, snaps, currentLane)
233
+ );
234
+
235
+ const activeTab = React.useMemo(
236
+ () => (activeTabIndex !== undefined ? tabs[activeTabIndex] : undefined),
237
+ [activeTabIndex, tabs]
238
+ );
153
239
 
154
- const [activeTabIndex, setActiveTab] = useState<number>(getActiveTabIndex());
240
+ React.useEffect(() => {
241
+ if (!currentLane) return;
242
+ if (tabs.length === 0) return;
243
+ const _activeTabIndex = getActiveTabIndex(currentVersion, tabs, tags, snaps, currentLane);
244
+ if (_activeTabIndex !== activeTabIndex) setActiveTab(_activeTabIndex);
245
+ }, [currentLane, tabs.length, tags?.length, snaps?.length, currentVersion, loading]);
155
246
 
156
247
  const multipleTabs = tabs.length > 1;
157
248
  const message = multipleTabs
158
249
  ? 'Switch to view tags, snaps, or lanes'
159
- : `Switch between ${tabs[0].name.toLocaleLowerCase()}s`;
250
+ : `Switch between ${tabs[0]?.name.toLocaleLowerCase()}s`;
251
+
252
+ const showTab = activeTabIndex !== undefined && tabs[activeTabIndex]?.payload.length > 0;
253
+
254
+ const _rowRenderer = React.useCallback(
255
+ function VersionRowRenderer({ index }) {
256
+ const { name, payload = [] } = activeTab || {};
257
+ const item = payload[index];
258
+ if (!item) return null;
259
+ if (name === 'LANE') {
260
+ const lane = item as LaneModel;
261
+ return <LaneInfo key={lane.id.toString()} currentLane={currentLane} {...lane}></LaneInfo>;
262
+ }
263
+ const version = item as DropdownComponentVersion;
264
+ return (
265
+ <VersionInfo
266
+ key={version.version}
267
+ currentVersion={currentVersion}
268
+ latestVersion={latestVersion}
269
+ overrideVersionHref={overrideVersionHref}
270
+ showDetails={showVersionDetails}
271
+ {...version}
272
+ ></VersionInfo>
273
+ );
274
+ },
275
+ [activeTab, currentVersion, latestVersion, showVersionDetails, currentLane?.id.toString(), showTab]
276
+ );
277
+
278
+ const rowRenderer = React.useMemo(
279
+ () => (showTab && activeTab ? _rowRenderer : () => null),
280
+ [showTab, activeTab, _rowRenderer]
281
+ );
282
+
283
+ const ActiveTab = React.useMemo(() => {
284
+ return activeTab?.payload.map((payload, index) => {
285
+ return rowRenderer({ index });
286
+ });
287
+ }, [activeTab]);
160
288
 
161
289
  return (
162
- <div {...rest}>
290
+ <div {...rest} className={classNames(styles.versionMenuContainer, !open && styles.hide)}>
163
291
  <div className={styles.top}>
164
- <div className={classNames(styles.titleContainer, styles.title)}>{message}</div>
165
- {localVersion && (
292
+ {loading && <LineSkeleton count={6} className={styles.loader} />}
293
+ {!loading && <div className={classNames(styles.titleContainer, styles.title)}>{message}</div>}
294
+ {!loading && localVersion && (
166
295
  <MenuLinkItem
167
296
  href={'?'}
168
297
  active={currentVersion === LOCAL_VERSION}
@@ -190,23 +319,7 @@ function VersionMenu({
190
319
  );
191
320
  })}
192
321
  </div>
193
- <div className={styles.versionContainer}>
194
- {tabs[activeTabIndex].name === 'LANE' &&
195
- tabs[activeTabIndex].payload.map((payload) => (
196
- <LaneInfo key={payload.id} currentLane={currentLane} {...payload}></LaneInfo>
197
- ))}
198
- {tabs[activeTabIndex].name !== 'LANE' &&
199
- tabs[activeTabIndex].payload.map((payload) => (
200
- <VersionInfo
201
- key={payload.version}
202
- currentVersion={currentVersion}
203
- latestVersion={latestVersion}
204
- overrideVersionHref={overrideVersionHref}
205
- showDetails={showVersionDetails}
206
- {...payload}
207
- ></VersionInfo>
208
- ))}
209
- </div>
322
+ <div className={styles.versionContainerRoot}>{ActiveTab}</div>
210
323
  </div>
211
324
  );
212
325
  }
@@ -4,41 +4,47 @@
4
4
  align-items: center;
5
5
  height: 40px;
6
6
  padding: 0 16px;
7
+ line-height: 24px;
7
8
  }
9
+
8
10
  .versionTimestamp {
9
11
  min-width: fit-content;
10
12
  margin-right: 2px;
11
13
  text-align: right;
12
14
  }
15
+
13
16
  .versionUserAvatar {
17
+ flex: none;
14
18
  padding: 0px 8px;
15
19
  }
20
+
16
21
  .laneIcon {
17
22
  padding: 0px 4px;
18
23
  }
24
+
19
25
  .version {
20
26
  max-width: 70%;
21
27
  display: flex;
22
28
  flex-direction: row;
23
- align-content: space-between;
24
29
  align-items: center;
30
+ justify-content: flex-start;
31
+ overflow: hidden;
25
32
  }
33
+
26
34
  .versionName {
27
35
  padding: 0px 8px;
28
- &.tag {
29
- min-width: fit-content;
30
- }
31
- &.snap {
32
- min-width: 20%;
33
- }
36
+ flex-grow: 1;
37
+ min-width: fit-content;
34
38
  }
35
39
 
36
- .commitMessage,
37
- .emptyMessage {
38
- min-width: 50%;
40
+ .commitMessage {
41
+ flex-grow: 1;
39
42
  padding: 0px 8px;
40
43
  }
44
+
41
45
  .emptyMessage {
42
46
  font-style: italic;
43
47
  color: var(--bit-text-color-light, #6c707c);
48
+ padding: 0px 8px;
49
+ font-size: (var-bit-p-xs, 14px);
44
50
  }
@@ -16,28 +16,37 @@ export type VersionInfoProps = DropdownComponentVersion & {
16
16
  showDetails?: boolean;
17
17
  };
18
18
 
19
- export function VersionInfo({
20
- version,
21
- currentVersion,
22
- latestVersion,
23
- date,
24
- username,
25
- email,
26
- overrideVersionHref,
27
- showDetails,
28
- message,
29
- tag,
30
- }: VersionInfoProps) {
19
+ export const VersionInfo = React.memo(React.forwardRef<HTMLDivElement, VersionInfoProps>(_VersionInfo));
20
+ function _VersionInfo(
21
+ {
22
+ version,
23
+ currentVersion,
24
+ latestVersion,
25
+ date,
26
+ username,
27
+ displayName,
28
+ email,
29
+ overrideVersionHref,
30
+ showDetails,
31
+ message,
32
+ tag,
33
+ profileImage,
34
+ }: VersionInfoProps,
35
+ ref?: React.ForwardedRef<HTMLDivElement>
36
+ ) {
31
37
  const isCurrent = version === currentVersion;
32
38
  const author = useMemo(() => {
33
39
  return {
34
- displayName: username,
40
+ displayName: displayName ?? '',
35
41
  email,
42
+ name: username ?? '',
43
+ profileImage,
36
44
  };
37
- }, [version]);
45
+ }, [displayName, email, username, profileImage]);
38
46
 
39
47
  const timestamp = useMemo(() => (date ? new Date(parseInt(date)).toString() : new Date().toString()), [date]);
40
48
  const currentVersionRef = useRef<HTMLDivElement>(null);
49
+
41
50
  useEffect(() => {
42
51
  if (isCurrent) {
43
52
  currentVersionRef.current?.scrollIntoView({ block: 'nearest' });
@@ -46,16 +55,20 @@ export function VersionInfo({
46
55
 
47
56
  const href = overrideVersionHref ? overrideVersionHref(version) : `?version=${version}`;
48
57
 
58
+ const formattedVersion = useMemo(() => {
59
+ return tag ? version : version.slice(0, 6);
60
+ }, [tag, version]);
61
+
62
+ const isLatest = version === latestVersion;
63
+
49
64
  return (
50
- <div ref={currentVersionRef}>
65
+ <div ref={ref || currentVersionRef}>
51
66
  <MenuLinkItem active={isCurrent} href={href} className={styles.versionRow}>
52
67
  <div className={styles.version}>
53
68
  <UserAvatar size={24} account={author} className={styles.versionUserAvatar} showTooltip={true} />
54
- <Ellipsis className={classNames(styles.versionName, tag && styles.tag, !tag && styles.snap)}>
55
- {version}
56
- </Ellipsis>
57
- {version === latestVersion && <VersionLabel className={styles.label} status="latest" />}
58
- {showDetails && commitMessage(message)}
69
+ <Ellipsis className={classNames(styles.versionName)}>{formattedVersion}</Ellipsis>
70
+ {isLatest && <VersionLabel status="latest" />}
71
+ <CommitMessage message={message} showDetails={showDetails} />
59
72
  </div>
60
73
  <Ellipsis className={styles.versionTimestamp}>
61
74
  <TimeAgo date={timestamp} />
@@ -65,7 +78,8 @@ export function VersionInfo({
65
78
  );
66
79
  }
67
80
 
68
- function commitMessage(message?: string) {
81
+ function CommitMessage({ message, showDetails }: { message?: string; showDetails?: boolean }) {
82
+ if (!showDetails) return null;
69
83
  if (!message || message === '') return <Ellipsis className={styles.emptyMessage}>No commit message</Ellipsis>;
70
84
  return <Ellipsis className={styles.commitMessage}>{message}</Ellipsis>;
71
85
  }