@teambit/ui-foundation.ui.side-bar 0.0.0-a9e2edbdd7f2d36a9ebd1ee8d02d04427ed71d54

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 (64) hide show
  1. package/component-tree/component-tree.module.scss +3 -0
  2. package/component-tree/component-tree.tsx +71 -0
  3. package/component-tree/component-view/component-view.module.scss +104 -0
  4. package/component-tree/component-view/component-view.tsx +249 -0
  5. package/component-tree/component-view/index.ts +1 -0
  6. package/component-tree/default-tree-node-renderer/default-tree-node-renderer.tsx +17 -0
  7. package/component-tree/default-tree-node-renderer/index.ts +1 -0
  8. package/component-tree/index.ts +6 -0
  9. package/component-tree/namespace-tree-node/index.ts +1 -0
  10. package/component-tree/namespace-tree-node/namespace-tree-node.module.scss +53 -0
  11. package/component-tree/namespace-tree-node/namespace-tree-node.tsx +63 -0
  12. package/component-tree/payload-type.tsx +10 -0
  13. package/component-tree/scope-tree-node/index.ts +1 -0
  14. package/component-tree/scope-tree-node/scope-tree-node.module.scss +53 -0
  15. package/component-tree/scope-tree-node/scope-tree-node.tsx +68 -0
  16. package/component-tree/utils/get-name.tsx +3 -0
  17. package/dist/component-tree/component-tree.d.ts +14 -0
  18. package/dist/component-tree/component-tree.js +42 -0
  19. package/dist/component-tree/component-tree.js.map +1 -0
  20. package/dist/component-tree/component-tree.module.scss +3 -0
  21. package/dist/component-tree/component-view/component-view.d.ts +7 -0
  22. package/dist/component-tree/component-view/component-view.js +163 -0
  23. package/dist/component-tree/component-view/component-view.js.map +1 -0
  24. package/dist/component-tree/component-view/component-view.module.scss +104 -0
  25. package/dist/component-tree/component-view/index.d.ts +1 -0
  26. package/dist/component-tree/component-view/index.js +2 -0
  27. package/dist/component-tree/component-view/index.js.map +1 -0
  28. package/dist/component-tree/default-tree-node-renderer/default-tree-node-renderer.d.ts +3 -0
  29. package/dist/component-tree/default-tree-node-renderer/default-tree-node-renderer.js +15 -0
  30. package/dist/component-tree/default-tree-node-renderer/default-tree-node-renderer.js.map +1 -0
  31. package/dist/component-tree/default-tree-node-renderer/index.d.ts +1 -0
  32. package/dist/component-tree/default-tree-node-renderer/index.js +2 -0
  33. package/dist/component-tree/default-tree-node-renderer/index.js.map +1 -0
  34. package/dist/component-tree/index.d.ts +6 -0
  35. package/dist/component-tree/index.js +6 -0
  36. package/dist/component-tree/index.js.map +1 -0
  37. package/dist/component-tree/namespace-tree-node/index.d.ts +1 -0
  38. package/dist/component-tree/namespace-tree-node/index.js +2 -0
  39. package/dist/component-tree/namespace-tree-node/index.js.map +1 -0
  40. package/dist/component-tree/namespace-tree-node/namespace-tree-node.d.ts +4 -0
  41. package/dist/component-tree/namespace-tree-node/namespace-tree-node.js +38 -0
  42. package/dist/component-tree/namespace-tree-node/namespace-tree-node.js.map +1 -0
  43. package/dist/component-tree/namespace-tree-node/namespace-tree-node.module.scss +53 -0
  44. package/dist/component-tree/payload-type.d.ts +6 -0
  45. package/dist/component-tree/payload-type.js +6 -0
  46. package/dist/component-tree/payload-type.js.map +1 -0
  47. package/dist/component-tree/scope-tree-node/index.d.ts +1 -0
  48. package/dist/component-tree/scope-tree-node/index.js +2 -0
  49. package/dist/component-tree/scope-tree-node/index.js.map +1 -0
  50. package/dist/component-tree/scope-tree-node/scope-tree-node.d.ts +4 -0
  51. package/dist/component-tree/scope-tree-node/scope-tree-node.js +38 -0
  52. package/dist/component-tree/scope-tree-node/scope-tree-node.js.map +1 -0
  53. package/dist/component-tree/scope-tree-node/scope-tree-node.module.scss +53 -0
  54. package/dist/component-tree/utils/get-name.d.ts +1 -0
  55. package/dist/component-tree/utils/get-name.js +4 -0
  56. package/dist/component-tree/utils/get-name.js.map +1 -0
  57. package/dist/index.d.ts +2 -0
  58. package/dist/index.js +2 -0
  59. package/dist/index.js.map +1 -0
  60. package/dist/preview-1752267760470.js +7 -0
  61. package/index.ts +3 -0
  62. package/package.json +62 -0
  63. package/types/asset.d.ts +41 -0
  64. package/types/style.d.ts +42 -0
@@ -0,0 +1,3 @@
1
+ .componentTree {
2
+ padding: 0 8px;
3
+ }
@@ -0,0 +1,71 @@
1
+ import { ComponentModel, useIdFromLocation } from '@teambit/component';
2
+ import React, { useMemo } from 'react';
3
+ import { useLocation } from '@teambit/base-react.navigation.link';
4
+ import { indentStyle } from '@teambit/base-ui.graph.tree.indent';
5
+ import { inflateToTree, attachPayload } from '@teambit/base-ui.graph.tree.inflate-paths';
6
+ import { Tree, TreeNodeRenderer, TreeNode as TreeNodeType } from '@teambit/design.ui.tree';
7
+ import { LanesModel } from '@teambit/lanes.ui.models.lanes-model';
8
+ import { TreeContextProvider } from '@teambit/base-ui.graph.tree.tree-context';
9
+ import { LanesContext } from '@teambit/lanes.hooks.use-lanes';
10
+ import { PayloadType, ScopePayload } from './payload-type';
11
+ import { DefaultTreeNodeRenderer } from './default-tree-node-renderer';
12
+
13
+ type ComponentTreeProps = {
14
+ components: ComponentModel[];
15
+ transformTree?: (rootNode: TreeNodeType) => TreeNodeType;
16
+ TreeNode?: TreeNodeRenderer<PayloadType>;
17
+ isCollapsed?: boolean;
18
+ lanesModel?: LanesModel;
19
+ // assumeScopeInUrl?: boolean;
20
+ } & React.HTMLAttributes<HTMLDivElement>;
21
+
22
+ function calcPayload(components: ComponentModel[]) {
23
+ const payloadMap = new Map<string, PayloadType>(components.map((c) => [c.id.toString({ ignoreVersion: true }), c]));
24
+
25
+ const scopeIds = new Set(components.map((x) => x.id.scope).filter((x) => !!x));
26
+ scopeIds.forEach((x) => x && payloadMap.set(`${x}/`, new ScopePayload()));
27
+
28
+ return payloadMap;
29
+ }
30
+
31
+ export function ComponentTree({
32
+ components,
33
+ isCollapsed,
34
+ className,
35
+ transformTree,
36
+ lanesModel,
37
+ // assumeScopeInUrl = false,
38
+ TreeNode = DefaultTreeNodeRenderer,
39
+ }: ComponentTreeProps) {
40
+ const { pathname = '/' } = useLocation() || {};
41
+ // override default splat from location when viewing a lane component
42
+ const laneCompUrl = pathname.split(LanesModel.baseLaneComponentRoute.concat('/'))[1];
43
+ const idFromLocation = useIdFromLocation(laneCompUrl, true);
44
+ const activeComponent = useMemo(() => {
45
+ const active = components.find((x) => {
46
+ return idFromLocation && (idFromLocation === x.id.fullName || idFromLocation === x.id.toStringWithoutVersion());
47
+ });
48
+ return active?.id.toString({ ignoreVersion: true });
49
+ }, [components.length, pathname]);
50
+
51
+ const rootNode = useMemo(() => {
52
+ const tree = inflateToTree<ComponentModel, PayloadType>(components, (c) => c.id.toString({ ignoreVersion: true }));
53
+
54
+ const payloadMap = calcPayload(components);
55
+
56
+ attachPayload(tree, payloadMap);
57
+
58
+ if (transformTree) return transformTree(tree);
59
+ return tree;
60
+ }, [components.length]);
61
+
62
+ return (
63
+ <LanesContext.Provider value={{ lanesModel }}>
64
+ <TreeContextProvider>
65
+ <div style={indentStyle(1)} className={className}>
66
+ <Tree TreeNode={TreeNode} activePath={activeComponent} tree={rootNode} isCollapsed={isCollapsed} />
67
+ </div>
68
+ </TreeContextProvider>
69
+ </LanesContext.Provider>
70
+ );
71
+ }
@@ -0,0 +1,104 @@
1
+ @import '@teambit/base-ui.theme.colors/colors.module.scss';
2
+
3
+ .component {
4
+ height: 32px;
5
+ display: flex;
6
+ align-items: center;
7
+ justify-content: space-between;
8
+ padding-right: 8px;
9
+ font-size: inherit;
10
+ font-size: var(--bit-p-xs);
11
+
12
+ //TODO - move to a link component
13
+ text-decoration: none;
14
+ color: inherit;
15
+
16
+ box-sizing: border-box;
17
+ border-left: 0px solid transparent;
18
+
19
+ &:hover {
20
+ background: var(--bit-bg-heavy, #f6f6f6);
21
+ .envIcon {
22
+ // filter: grayscale(0);
23
+ }
24
+ }
25
+
26
+ &.active {
27
+ background: var(--bit-accent-bg, #eceaff);
28
+ .envIcon {
29
+ // filter: grayscale(0);
30
+ }
31
+ // .right {
32
+ // .componentIcon {
33
+ // color: $w10;
34
+ // }
35
+ // }
36
+ }
37
+
38
+ .icon {
39
+ width: 16px;
40
+ height: 16px;
41
+ opacity: 0.8;
42
+ }
43
+
44
+ .left {
45
+ display: flex;
46
+ align-items: center;
47
+ flex: 1;
48
+ min-width: 0;
49
+ line-height: 24px;
50
+
51
+ > span {
52
+ overflow: hidden;
53
+ text-overflow: ellipsis;
54
+ white-space: nowrap;
55
+ padding: 0 8px;
56
+ }
57
+ }
58
+ .envIcon {
59
+ transition: filter 300ms ease-in-out;
60
+ // filter: grayscale(1);
61
+ width: 16px;
62
+ height: 16px;
63
+ border-radius: 4px;
64
+ }
65
+
66
+ .right {
67
+ display: flex;
68
+ align-items: center;
69
+ font-size: 12px;
70
+
71
+ > * {
72
+ margin-right: 4px;
73
+ }
74
+ > :last-child() {
75
+ margin-right: 0px;
76
+ }
77
+ }
78
+ }
79
+
80
+ .componentEnvTooltip {
81
+ font-size: var(--bit-p-xs);
82
+ max-width: unset !important; // override tippy's default max-width: 350px
83
+ .componentEnvTitle {
84
+ font-size: var(--bit-p-xxs, 12px);
85
+ color: #c9c3f6;
86
+ font-weight: bold;
87
+ margin-bottom: 4px;
88
+ }
89
+ }
90
+
91
+ .tooltip {
92
+ max-width: unset !important; // override tippy's default max-width: 350px
93
+ font-size: var(--bit-p-xxs);
94
+ opacity: 100%;
95
+ }
96
+
97
+ .envLink {
98
+ color: white;
99
+ text-decoration: none;
100
+ }
101
+
102
+ .opacity {
103
+ opacity: 50%;
104
+ }
@@ -0,0 +1,249 @@
1
+ /* eslint-disable complexity */
2
+ import { ComponentTreeSlot } from '@teambit/component-tree';
3
+ import { Link, useLocation } from '@teambit/base-react.navigation.link';
4
+ import { EnvIcon } from '@teambit/envs.ui.env-icon';
5
+ import { DeprecationIcon } from '@teambit/component.ui.deprecation-icon';
6
+ import classNames from 'classnames';
7
+ import { ComponentID } from '@teambit/component-id';
8
+ import { ComponentModel } from '@teambit/component';
9
+ import { ComponentUrl } from '@teambit/component.modules.component-url';
10
+ import React, { useCallback, useContext, useEffect, useState } from 'react';
11
+ import { Tooltip } from '@teambit/design.ui.tooltip';
12
+ import { TreeContext } from '@teambit/base-ui.graph.tree.tree-context';
13
+ import { indentClass } from '@teambit/base-ui.graph.tree.indent';
14
+ import { TreeNodeProps } from '@teambit/base-ui.graph.tree.recursive-tree';
15
+ import { LanesContext } from '@teambit/lanes.hooks.use-lanes';
16
+ import { LanesModel } from '@teambit/lanes.ui.models.lanes-model';
17
+ import { getName } from '../utils/get-name';
18
+
19
+ import styles from './component-view.module.scss';
20
+
21
+ export type ComponentViewProps<Payload = any> = {
22
+ treeNodeSlot?: ComponentTreeSlot;
23
+ scopeName?: string;
24
+ } & TreeNodeProps<Payload>;
25
+
26
+ export function ComponentView(props: ComponentViewProps) {
27
+ const { node, scopeName, treeNodeSlot } = props;
28
+
29
+ let component = node.payload;
30
+
31
+ const { onSelect } = useContext(TreeContext);
32
+ const lanesContextModel = useContext(LanesContext);
33
+ const lanesModel = lanesContextModel?.lanesModel;
34
+ const [mounted, setMounted] = useState(false);
35
+
36
+ useEffect(() => {
37
+ setMounted(true);
38
+ return () => {
39
+ setMounted(false);
40
+ };
41
+ }, []);
42
+
43
+ const handleClick = useCallback(
44
+ (event: React.MouseEvent<HTMLAnchorElement, MouseEvent>) => {
45
+ onSelect?.(node.id, event);
46
+ },
47
+ [onSelect, node.id]
48
+ );
49
+
50
+ const location = useLocation();
51
+ const scope = { name: scopeName };
52
+
53
+ const isMissingCompOrEnvId = !component?.id || !component?.environment?.id;
54
+
55
+ component = component as ComponentModel;
56
+
57
+ const envId = !isMissingCompOrEnvId ? ComponentID.fromString(component.environment?.id as string) : undefined;
58
+ const isEnvOnCurrentLane = envId
59
+ ? lanesModel?.isComponentOnLaneButNotOnMain(envId, false, lanesModel.viewedLane?.id)
60
+ : false;
61
+
62
+ const envUrl = !envId
63
+ ? undefined
64
+ : (isEnvOnCurrentLane &&
65
+ lanesModel?.viewedLane?.id &&
66
+ mounted &&
67
+ `${window.location.origin}${LanesModel.getLaneComponentUrl(envId, lanesModel.viewedLane?.id, undefined, lanesModel.viewedLane)}`) ||
68
+ ComponentUrl.toUrl(envId, {
69
+ includeVersion: true,
70
+ useLocationOrigin: mounted ? window.location.host.startsWith('localhost') : false,
71
+ });
72
+
73
+ const envTooltip = envId ? (
74
+ <Link
75
+ external
76
+ className={styles.envLink}
77
+ href={envUrl}
78
+ onClick={(event) => {
79
+ // do not trigger component selection
80
+ event.stopPropagation();
81
+ }}
82
+ >
83
+ <div className={styles.componentEnvTitle}>Environment</div>
84
+ <div>{component.environment?.id}</div>
85
+ </Link>
86
+ ) : null;
87
+
88
+ const href = !isMissingCompOrEnvId
89
+ ? lanesModel?.getLaneComponentUrlByVersion(
90
+ component.id as any,
91
+ lanesModel.viewedLane?.id,
92
+ !scope.name,
93
+ lanesModel.viewedLane
94
+ )
95
+ : undefined;
96
+
97
+ const viewingMainCompOnLane = React.useMemo(() => {
98
+ if (isMissingCompOrEnvId) return false;
99
+ return (
100
+ !component.status?.isNew &&
101
+ !lanesModel?.viewedLane?.id.isDefault() &&
102
+ lanesModel?.isComponentOnMainButNotOnLane(component.id as any, undefined, lanesModel?.viewedLane?.id)
103
+ );
104
+ }, [lanesModel?.viewedLane?.id.toString(), component.id.toString()]);
105
+
106
+ /**
107
+ * this covers the following use cases when matching the active component's href with location
108
+ *
109
+ * 1. viewing main component
110
+ *
111
+ * - /<component-id>/
112
+ * - /<component-id>/~sub-route
113
+ * - /<component-id>/~sub-route/another-sub-route
114
+ * - /<component-id>/~sub-route?version=<version>
115
+ * - /<component-id>/~sub-route?scope=<scope>
116
+ * - /<component-id>?scope=<scope>
117
+ *
118
+ * 2. viewing a lane component
119
+ *
120
+ * - /~lane/<lane-id>/<component-id>/
121
+ * - /~lane/<lane-id>/<component-id>?scope=<scope>
122
+ * - /~lane/<lane-id>/<component-id>/~sub-route
123
+ * - /~lane/<lane-id>/<component-id>/~sub-route/another-sub-route
124
+ * - /~lane/<lane-id>/<component-id>/~sub-route?version=<version>
125
+ * - /~lane/<lane-id>/<component-id>/~sub-route?scope=<scope>
126
+ *
127
+ * 3. viewing a main component when on a lane
128
+ *
129
+ * - /?lane=<lane-id>/<component-id>/
130
+ * - /?lane=<lane-id>/<component-id>?scope=<scope>
131
+ * - /?lane=<lane-id>/<component-id>/~sub-route
132
+ * - /?lane=<lane-id>/<component-id>/~sub-route/another-sub-route
133
+ * - /?lane=<lane-id>/<component-id>/~sub-route?version=<version>
134
+ * - /?lane=<lane-id>/<component-id>/~sub-route?scope=<scope>
135
+ *
136
+ * 4. viewing currently checked out lane component on workspace
137
+ *
138
+ * - /<component-id-without-scope>/
139
+ * - /<component-id-without-scope>?scope=<scope>
140
+ *
141
+ * 5. viewing lane component from the same scope as the lane on a workspace
142
+ *
143
+ * - /~lane/<lane-id>/<component-id-without-scope>/
144
+ *
145
+ * @todo - move this logic to a util function
146
+ */
147
+ const isActive = React.useMemo(() => {
148
+ if (!href || !location || !component?.id) return false;
149
+
150
+ const searchParams = new URLSearchParams(location.search);
151
+ const scopeFromQueryParams = searchParams.get('scope');
152
+ const pathname = location.pathname.substring(1).split('?')[0];
153
+ const compIdStr = component.id.toStringWithoutVersion();
154
+ const compIdName = component.id.fullName;
155
+ const componentScope = component.id.scope;
156
+ const locationIncludesLaneId = location.pathname.includes(LanesModel.lanesPrefix);
157
+
158
+ const sanitizedHref = (href.startsWith('/') ? href.substring(1) : href).split('?')[0];
159
+
160
+ // if you are in a workspace, the componentId might not have a scopeId, if you are viewing
161
+ // a component on the checked out lane
162
+ const viewingCheckedOutLaneComp =
163
+ lanesModel?.currentLane && lanesModel.currentLane.id.toString() === lanesModel?.viewedLane?.id.toString();
164
+
165
+ if (
166
+ !locationIncludesLaneId &&
167
+ (lanesModel?.viewedLane?.id.isDefault() || viewingMainCompOnLane || viewingCheckedOutLaneComp)
168
+ ) {
169
+ // split out any sub routes if exist
170
+ const compUrl = pathname.split('/~')[0];
171
+ // may or may not contain scope
172
+ const scopeUrl = scope.name ? `${scope.name.replace('.', '/')}/` : '';
173
+ const compUrlWithoutScope = compUrl.replace(scopeUrl, '');
174
+ return !scopeFromQueryParams
175
+ ? sanitizedHref === compUrlWithoutScope
176
+ : sanitizedHref === compUrl && componentScope === scopeFromQueryParams;
177
+ }
178
+
179
+ const laneCompUrlWithSubRoutes = pathname.split(LanesModel.baseLaneComponentRoute)[1] ?? '';
180
+
181
+ const extractedLaneCompUrl = laneCompUrlWithSubRoutes.split('/~')[0] ?? '';
182
+ const laneCompUrl = extractedLaneCompUrl.startsWith('/') ? extractedLaneCompUrl.substring(1) : extractedLaneCompUrl;
183
+
184
+ /**
185
+ * if the laneCompUrl doesn't have the scope as part of it and you are on a bare scope
186
+ * attach the bare scope to the laneCompUrl and parse it as a ComponentID
187
+ */
188
+ const laneCompIdFromUrl =
189
+ ComponentID.tryFromString(laneCompUrl) ??
190
+ (scope.name ? ComponentID.tryFromString(`${scope.name}/${laneCompUrl}`) : undefined);
191
+
192
+ // viewing lane component from the same scope as the lane on a workspace
193
+ const laneAndCompFromSameScopeOnWs =
194
+ !scope.name && lanesModel?.viewedLane?.id && component.id.scope === lanesModel?.viewedLane?.id.scope;
195
+
196
+ if (laneAndCompFromSameScopeOnWs) {
197
+ return !scopeFromQueryParams
198
+ ? compIdName === laneCompUrl
199
+ : compIdName === laneCompUrl && componentScope === scopeFromQueryParams;
200
+ }
201
+
202
+ if (!laneCompIdFromUrl) return false;
203
+
204
+ return !scopeFromQueryParams
205
+ ? laneCompIdFromUrl?.toString() === compIdStr || laneCompIdFromUrl.fullName === compIdName
206
+ : laneCompIdFromUrl?.toString() === compIdStr ||
207
+ (laneCompIdFromUrl.fullName === compIdName && componentScope === scopeFromQueryParams);
208
+ }, [href, viewingMainCompOnLane, location?.pathname, component?.id.toString(), scope.name]);
209
+
210
+ if (isMissingCompOrEnvId) return null;
211
+
212
+ const isCompModified =
213
+ component.status?.modifyInfo?.hasModifiedFiles || component.status?.modifyInfo?.hasModifiedDependencies;
214
+
215
+ const addOpacity = viewingMainCompOnLane && !isCompModified;
216
+
217
+ const Name = viewingMainCompOnLane ? (
218
+ <Tooltip className={styles.tooltip} placement="top" content="On Main">
219
+ <span className={classNames(addOpacity && styles.opacity)}>{getName(node.id)}</span>
220
+ </Tooltip>
221
+ ) : (
222
+ <span>{getName(node.id)}</span>
223
+ );
224
+
225
+ return (
226
+ <Link
227
+ href={href}
228
+ className={classNames(indentClass, styles.component)}
229
+ activeClassName={styles.active}
230
+ onClick={handleClick}
231
+ // exact={true}
232
+ active={isActive}
233
+ >
234
+ <div className={styles.left}>
235
+ <Tooltip className={styles.componentEnvTooltip} placement="top" content={envTooltip}>
236
+ <EnvIcon component={component} className={styles.envIcon} />
237
+ </Tooltip>
238
+ {Name}
239
+ </div>
240
+
241
+ <div className={styles.right}>
242
+ <DeprecationIcon component={component} />
243
+ {/* {isInternal && <Icon of="Internal" className={styles.componentIcon} />} */}
244
+ {treeNodeSlot &&
245
+ treeNodeSlot.toArray().map(([id, treeNode]) => <treeNode.widget key={id} component={component} />)}
246
+ </div>
247
+ </Link>
248
+ );
249
+ }
@@ -0,0 +1 @@
1
+ export { ComponentView } from './component-view';
@@ -0,0 +1,17 @@
1
+ import React from 'react';
2
+ import type { TreeNodeProps } from '@teambit/base-ui.graph.tree.recursive-tree';
3
+ import { PayloadType, ScopePayload } from '../payload-type';
4
+
5
+ import { ComponentView } from '../component-view';
6
+ import { ScopeTreeNode } from '../scope-tree-node';
7
+ import { NamespaceTreeNode } from '../namespace-tree-node';
8
+
9
+ export function DefaultTreeNodeRenderer(props: TreeNodeProps<PayloadType>) {
10
+ const { node } = props;
11
+ const { children, payload } = node;
12
+ if (!children) return <ComponentView {...props} />;
13
+
14
+ if (payload instanceof ScopePayload) return <ScopeTreeNode {...props} />;
15
+
16
+ return <NamespaceTreeNode {...props} />;
17
+ }
@@ -0,0 +1 @@
1
+ export { DefaultTreeNodeRenderer } from './default-tree-node-renderer';
@@ -0,0 +1,6 @@
1
+ export * from './component-tree';
2
+ export { ComponentView } from './component-view';
3
+ export { ScopeTreeNode } from './scope-tree-node';
4
+ export { NamespaceTreeNode } from './namespace-tree-node';
5
+ export { ScopePayload } from './payload-type';
6
+ export type { PayloadType } from './payload-type';
@@ -0,0 +1 @@
1
+ export { NamespaceTreeNode } from './namespace-tree-node';
@@ -0,0 +1,53 @@
1
+ @import '@teambit/base-ui.theme.colors/colors.module.scss';
2
+
3
+ .namespace {
4
+ position: relative;
5
+ height: 32px;
6
+ display: flex;
7
+ align-items: center;
8
+ justify-content: space-between;
9
+ user-select: none;
10
+ font-weight: bold;
11
+ font-size: inherit;
12
+ padding-right: 8px;
13
+ font-size: var(--bit-p-xs);
14
+ cursor: pointer;
15
+ &:hover {
16
+ background: var(--bit-bg-heavy, #f6f6f6);
17
+ }
18
+
19
+ .arrow {
20
+ display: inline-block;
21
+ line-height: inherit;
22
+ transition: all 300ms;
23
+ margin-right: 10px;
24
+ color: var(--bit-text-color-light, #6c707c);
25
+ &.collapsed {
26
+ transform: rotate(-0.25turn);
27
+ }
28
+ }
29
+ }
30
+
31
+ .highlighted {
32
+ color: var(--bit-accent-heavy, #5d4aec);
33
+ }
34
+
35
+ .left {
36
+ display: flex;
37
+ align-items: center;
38
+ flex: 1;
39
+ min-width: 0;
40
+ .name {
41
+ overflow: hidden;
42
+ text-overflow: ellipsis;
43
+ white-space: nowrap;
44
+ margin-right: 8px;
45
+ line-height: 24px;
46
+ }
47
+ }
48
+
49
+ .componentTree {
50
+ overflow: hidden;
51
+ transition: max-height 400ms ease-in-out;
52
+ font-size: var(--bit-p-xs);
53
+ }
@@ -0,0 +1,63 @@
1
+ import { Icon } from '@teambit/evangelist.elements.icon';
2
+ import classNames from 'classnames';
3
+ import React, { useState, useEffect, useRef } from 'react';
4
+ import AnimateHeight from 'react-animate-height';
5
+ import { indentClass, indentStyle } from '@teambit/base-ui.graph.tree.indent';
6
+ import { useTree, TreeNodeProps, TreeLayer } from '@teambit/design.ui.tree';
7
+ import { PayloadType } from '../payload-type';
8
+ import { getName } from '../utils/get-name';
9
+ import styles from './namespace-tree-node.module.scss';
10
+
11
+ export type NamespaceTreeNodeProps = {} & TreeNodeProps<PayloadType>;
12
+
13
+ export function NamespaceTreeNode({ node, depth }: NamespaceTreeNodeProps) {
14
+ const { isCollapsed, activePath } = useTree();
15
+ const isActive = activePath?.startsWith(node.id);
16
+
17
+ const initialOpen = isActive || !isCollapsed;
18
+ const [open, toggle] = useState<boolean | void>(initialOpen);
19
+
20
+ const firstRun = useRef(true);
21
+ useEffect(() => {
22
+ const { current } = firstRun;
23
+ if (current) return;
24
+ if (isActive === true) toggle(true);
25
+ }, [isActive]);
26
+
27
+ useEffect(() => {
28
+ if (firstRun.current) return;
29
+ toggle(!isCollapsed);
30
+ }, [isCollapsed]);
31
+
32
+ useEffect(() => {
33
+ firstRun.current = false;
34
+ }, []);
35
+
36
+ const displayName = getName(node.id.replace(/\/$/, ''));
37
+ const highlighted = !open && isActive;
38
+ return (
39
+ <div>
40
+ {node.id && (
41
+ <div
42
+ className={classNames(indentClass, styles.namespace, highlighted && styles.highlighted)}
43
+ onClick={() => toggle(!open)}
44
+ tabIndex={0}
45
+ role="button"
46
+ onKeyDown={(e) => {
47
+ if (e.key === 'Enter') toggle(!open);
48
+ }}
49
+ >
50
+ <div className={styles.left}>
51
+ <Icon className={classNames(styles.arrow, !open && styles.collapsed)} of="fat-arrow-down" />
52
+ <span className={styles.name}>{displayName}</span>
53
+ </div>
54
+ </div>
55
+ )}
56
+ <AnimateHeight height={open ? 'auto' : 0}>
57
+ <div style={indentStyle(depth + 1)} className={classNames(styles.componentTree)}>
58
+ {node.children && <TreeLayer childNodes={node.children} depth={depth} />}
59
+ </div>
60
+ </AnimateHeight>
61
+ </div>
62
+ );
63
+ }
@@ -0,0 +1,10 @@
1
+ import { ComponentModel } from '@teambit/component';
2
+ import { LaneModel } from '@teambit/lanes.ui.models.lanes-model';
3
+
4
+ export class ScopePayload {
5
+ get isScope() {
6
+ return true;
7
+ }
8
+ }
9
+
10
+ export type PayloadType = ComponentModel | ScopePayload | LaneModel | undefined;
@@ -0,0 +1 @@
1
+ export { ScopeTreeNode } from './scope-tree-node';
@@ -0,0 +1,53 @@
1
+ @import '@teambit/base-ui.theme.colors/colors.module.scss';
2
+
3
+ .scope {
4
+ position: relative;
5
+ height: 32px;
6
+ display: flex;
7
+ align-items: center;
8
+ justify-content: space-between;
9
+ user-select: none;
10
+ cursor: pointer;
11
+ font-weight: bold;
12
+ font-size: inherit;
13
+ padding-right: 8px;
14
+ font-size: var(--bit-p-xs);
15
+ &:hover {
16
+ background: var(--bit-bg-heavy, #f6f6f6);
17
+ }
18
+
19
+ .arrow {
20
+ display: inline-block;
21
+ line-height: inherit;
22
+ transition: all 300ms;
23
+ margin-right: 10px;
24
+ color: var(--bit-text-color-light, #6c707c);
25
+ &.collapsed {
26
+ transform: rotate(-0.25turn);
27
+ }
28
+ }
29
+ }
30
+
31
+ .highlighted {
32
+ color: var(--bit-accent-heavy, #5d4aec);
33
+ }
34
+
35
+ .left {
36
+ display: flex;
37
+ align-items: center;
38
+ flex: 1;
39
+ min-width: 0;
40
+ .name {
41
+ overflow: hidden;
42
+ text-overflow: ellipsis;
43
+ white-space: nowrap;
44
+ margin-right: 8px;
45
+ line-height: 115%;
46
+ }
47
+ }
48
+
49
+ .componentTree {
50
+ overflow: hidden;
51
+ transition: max-height 400ms ease-in-out;
52
+ font-size: var(--bit-p-xs);
53
+ }