@teambit/workspace 1.0.982 → 1.0.984

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 (44) hide show
  1. package/dist/{preview-1778613913688.js → preview-1778681054666.js} +2 -2
  2. package/dist/ui/workspace/workspace-overview/card-overlays.d.ts +4 -0
  3. package/dist/ui/workspace/workspace-overview/card-overlays.js +133 -0
  4. package/dist/ui/workspace/workspace-overview/card-overlays.js.map +1 -0
  5. package/dist/ui/workspace/workspace-overview/card-overlays.module.scss +72 -0
  6. package/dist/ui/workspace/workspace-overview/filter-utils.d.ts +4 -1
  7. package/dist/ui/workspace/workspace-overview/filter-utils.js +17 -1
  8. package/dist/ui/workspace/workspace-overview/filter-utils.js.map +1 -1
  9. package/dist/ui/workspace/workspace-overview/hope-component-card.d.ts +14 -0
  10. package/dist/ui/workspace/workspace-overview/hope-component-card.js +186 -0
  11. package/dist/ui/workspace/workspace-overview/hope-component-card.js.map +1 -0
  12. package/dist/ui/workspace/workspace-overview/hope-component-card.module.scss +162 -0
  13. package/dist/ui/workspace/workspace-overview/namespace-header.d.ts +8 -0
  14. package/dist/ui/workspace/workspace-overview/namespace-header.js +107 -0
  15. package/dist/ui/workspace/workspace-overview/namespace-header.js.map +1 -0
  16. package/dist/ui/workspace/workspace-overview/namespace-header.module.scss +88 -0
  17. package/dist/ui/workspace/workspace-overview/use-workspace-aggregation.js +11 -5
  18. package/dist/ui/workspace/workspace-overview/use-workspace-aggregation.js.map +1 -1
  19. package/dist/ui/workspace/workspace-overview/workspace-filter-panel.js +8 -6
  20. package/dist/ui/workspace/workspace-overview/workspace-filter-panel.js.map +1 -1
  21. package/dist/ui/workspace/workspace-overview/workspace-overview.d.ts +0 -6
  22. package/dist/ui/workspace/workspace-overview/workspace-overview.js +38 -105
  23. package/dist/ui/workspace/workspace-overview/workspace-overview.js.map +1 -1
  24. package/dist/ui/workspace/workspace-overview/workspace-overview.module.scss +38 -158
  25. package/dist/ui/workspace/workspace-overview/workspace-overview.types.d.ts +6 -0
  26. package/dist/ui/workspace/workspace-overview/workspace-overview.types.js.map +1 -1
  27. package/dist/ui/workspace/workspace.js +22 -5
  28. package/dist/ui/workspace/workspace.js.map +1 -1
  29. package/dist/ui/workspace/workspace.module.scss +38 -30
  30. package/package.json +43 -44
  31. package/ui/workspace/workspace-overview/card-overlays.module.scss +72 -0
  32. package/ui/workspace/workspace-overview/card-overlays.tsx +66 -0
  33. package/ui/workspace/workspace-overview/filter-utils.ts +18 -1
  34. package/ui/workspace/workspace-overview/hope-component-card.module.scss +162 -0
  35. package/ui/workspace/workspace-overview/hope-component-card.tsx +152 -0
  36. package/ui/workspace/workspace-overview/namespace-header.module.scss +88 -0
  37. package/ui/workspace/workspace-overview/namespace-header.tsx +72 -0
  38. package/ui/workspace/workspace-overview/use-workspace-aggregation.ts +11 -5
  39. package/ui/workspace/workspace-overview/workspace-filter-panel.tsx +10 -9
  40. package/ui/workspace/workspace-overview/workspace-overview.module.scss +38 -158
  41. package/ui/workspace/workspace-overview/workspace-overview.tsx +40 -88
  42. package/ui/workspace/workspace-overview/workspace-overview.types.ts +7 -1
  43. package/ui/workspace/workspace.module.scss +38 -30
  44. package/ui/workspace/workspace.tsx +24 -7
@@ -0,0 +1,162 @@
1
+ .card {
2
+ position: relative;
3
+ border-radius: 10px;
4
+ overflow: hidden;
5
+ background: var(--surface-color);
6
+ border: 1px solid var(--border-medium-color);
7
+ cursor: pointer;
8
+ transition:
9
+ box-shadow 0.15s ease,
10
+ border-color 0.15s ease;
11
+
12
+ &:hover {
13
+ box-shadow: 0 4px 14px rgba(20, 0, 104, 0.06);
14
+ }
15
+ }
16
+
17
+ .cardBuilding {
18
+ composes: card;
19
+
20
+ &:hover {
21
+ box-shadow: none;
22
+ }
23
+ }
24
+
25
+ .linkWrapper {
26
+ text-decoration: none;
27
+ color: inherit;
28
+ display: block;
29
+ }
30
+
31
+ /* ---- Preview ---- */
32
+
33
+ .preview {
34
+ height: 180px;
35
+ position: relative;
36
+ overflow: hidden;
37
+ background: white;
38
+ border-bottom: 1px solid var(--border-medium-color);
39
+ }
40
+
41
+ .previewQueued {
42
+ composes: preview;
43
+ background: var(--surface01-color);
44
+ }
45
+
46
+ .previewInner {
47
+ position: absolute;
48
+ inset: 0;
49
+ overflow: hidden;
50
+ }
51
+
52
+ /* ---- Env badge ---- */
53
+
54
+ .envBadge {
55
+ position: absolute;
56
+ bottom: 8px;
57
+ right: 8px;
58
+ background: var(--surface-color);
59
+ padding: 4px;
60
+ border-radius: 8px;
61
+ box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
62
+ display: flex;
63
+ align-items: center;
64
+ justify-content: center;
65
+ width: 24px;
66
+ height: 24px;
67
+ z-index: 3;
68
+ }
69
+
70
+ .envIcon {
71
+ height: 14px;
72
+ display: block;
73
+ }
74
+
75
+ /* ---- Status corner ---- */
76
+
77
+ .statusCorner {
78
+ position: absolute;
79
+ top: 10px;
80
+ right: 10px;
81
+ z-index: 2;
82
+ }
83
+
84
+ /* ---- Footer ---- */
85
+
86
+ .footer {
87
+ padding: 8px 12px;
88
+ display: flex;
89
+ align-items: center;
90
+ gap: 8px;
91
+ }
92
+
93
+ .name {
94
+ flex: 1;
95
+ min-width: 0;
96
+ font-size: 12.5px;
97
+ font-weight: 500;
98
+ font-family: 'JetBrains Mono', ui-monospace, Menlo, monospace;
99
+ color: var(--on-background-color);
100
+ overflow: hidden;
101
+ text-overflow: ellipsis;
102
+ white-space: nowrap;
103
+ letter-spacing: -0.01em;
104
+ }
105
+
106
+ .nameQueued {
107
+ composes: name;
108
+ color: var(--on-background-medium-color);
109
+ }
110
+
111
+ .hash {
112
+ font-size: 10.5px;
113
+ padding: 1px 6px;
114
+ border-radius: 4px;
115
+ background: var(--surface01-color);
116
+ font-family: 'JetBrains Mono', ui-monospace, Menlo, monospace;
117
+ color: var(--on-background-low-color);
118
+ flex-shrink: 0;
119
+ }
120
+
121
+ .scopeBadge {
122
+ width: 20px;
123
+ height: 20px;
124
+ border-radius: 5px;
125
+ display: flex;
126
+ align-items: center;
127
+ justify-content: center;
128
+ flex-shrink: 0;
129
+ overflow: hidden;
130
+ }
131
+
132
+ .scopeBadgeIcon {
133
+ width: 12px;
134
+ height: 12px;
135
+ object-fit: contain;
136
+ filter: brightness(0) invert(1);
137
+ }
138
+
139
+ .scopeBadgeInitial {
140
+ font-family: 'JetBrains Mono', ui-monospace, Menlo, monospace;
141
+ font-size: 10px;
142
+ font-weight: 700;
143
+ color: white;
144
+ line-height: 1;
145
+ }
146
+
147
+ .buildingLabel {
148
+ font-size: 10px;
149
+ font-weight: 600;
150
+ letter-spacing: 0.06em;
151
+ text-transform: uppercase;
152
+ flex-shrink: 0;
153
+ }
154
+
155
+ /* ---- Load preview button ---- */
156
+
157
+ .loadPreview {
158
+ position: absolute;
159
+ right: 4px !important;
160
+ left: unset !important;
161
+ z-index: 4;
162
+ }
@@ -0,0 +1,152 @@
1
+ import React, { useEffect, useRef, useState } from 'react';
2
+ import { Link } from '@teambit/base-react.navigation.link';
3
+ import { PreviewPlaceholder } from '@teambit/preview.ui.preview-placeholder';
4
+ import { Tooltip } from '@teambit/design.ui.tooltip';
5
+ import { LoadPreview } from '@teambit/workspace.ui.load-preview';
6
+ import { ComponentID } from '@teambit/component-id';
7
+ import type { ComponentModel } from '@teambit/component';
8
+ import type { ComponentDescriptor } from '@teambit/component-descriptor';
9
+ import type { ScopeID } from '@teambit/scopes.scope-id';
10
+ import { getComponentStatus } from './filter-utils';
11
+ import { ChangedPill, BuildSpinner, BuildingPreview } from './card-overlays';
12
+ import styles from './hope-component-card.module.scss';
13
+
14
+ export type HopeComponentCardProps = {
15
+ component: ComponentModel;
16
+ componentDescriptor: ComponentDescriptor;
17
+ scope?: { id: ScopeID; icon?: string; backgroundIconColor?: string };
18
+ showPreview?: boolean;
19
+ };
20
+
21
+ export function HopeComponentCard({
22
+ component,
23
+ componentDescriptor,
24
+ scope,
25
+ showPreview: showPreviewProp,
26
+ }: HopeComponentCardProps) {
27
+ const [shouldShowPreview, setShouldShowPreview] = useState(Boolean(showPreviewProp));
28
+ const prevServerUrlRef = useRef(component.server?.url);
29
+
30
+ useEffect(() => {
31
+ if (prevServerUrlRef.current !== component.server?.url && shouldShowPreview) {
32
+ setShouldShowPreview(false);
33
+ setTimeout(() => setShouldShowPreview(true), 50);
34
+ }
35
+ prevServerUrlRef.current = component.server?.url;
36
+ }, [component.server?.url]);
37
+
38
+ useEffect(() => {
39
+ setShouldShowPreview(Boolean(showPreviewProp));
40
+ }, [showPreviewProp]);
41
+
42
+ const item = { component } as any;
43
+ const status = getComponentStatus(item);
44
+ const accent = 'var(--bit-accent-color, #6c5ce7)';
45
+
46
+ const isBuilding = status === 'building';
47
+ const isQueued = status === 'queued';
48
+ const isChanged = status === 'changed';
49
+
50
+ const href = `${component.id.fullName}?scope=${component.id.scope}`;
51
+
52
+ const loadPreviewVisible = component.compositions.length > 0 && !isBuilding && !shouldShowPreview;
53
+
54
+ const showPreviewClick = (e: React.MouseEvent) => {
55
+ e.stopPropagation();
56
+ e.preventDefault();
57
+ setShouldShowPreview(true);
58
+ };
59
+
60
+ const envAspect = componentDescriptor.get<any>('teambit.envs/envs');
61
+ const env = envAspect?.data || envAspect;
62
+ const envComponentId = env?.id ? ComponentID.fromString(env.id) : undefined;
63
+
64
+ const cardClass = isBuilding ? styles.cardBuilding : styles.card;
65
+ const buildingBorderStyle = isBuilding
66
+ ? {
67
+ borderColor: accent,
68
+ boxShadow: `0 0 0 3px color-mix(in srgb, var(--bit-accent-color, #6c5ce7) 10%, transparent)`,
69
+ }
70
+ : undefined;
71
+
72
+ const nameLabel = component.id.namespace ? `${component.id.namespace}/${component.id.name}` : component.id.name;
73
+
74
+ const shortHash = component.id.version?.slice(0, 7);
75
+
76
+ const scopeInitial = component.id.scope?.split('.').pop()?.charAt(0).toUpperCase();
77
+
78
+ return (
79
+ <div className={cardClass} style={buildingBorderStyle}>
80
+ {loadPreviewVisible && <LoadPreview className={styles.loadPreview} onClick={showPreviewClick} />}
81
+
82
+ <Link href={href} className={styles.linkWrapper}>
83
+ <div className={styles.preview}>
84
+ <div className={styles.previewInner}>
85
+ <CardPreview
86
+ component={component}
87
+ componentDescriptor={componentDescriptor}
88
+ status={status}
89
+ shouldShowPreview={shouldShowPreview}
90
+ />
91
+ </div>
92
+
93
+ {!isQueued && env?.icon && (
94
+ <div className={styles.envBadge}>
95
+ <Tooltip delay={300} content={envComponentId?.name}>
96
+ <img src={env.icon} className={styles.envIcon} alt="" />
97
+ </Tooltip>
98
+ </div>
99
+ )}
100
+
101
+ {(isChanged || isBuilding) && (
102
+ <div className={styles.statusCorner}>
103
+ {isChanged && <ChangedPill />}
104
+ {isBuilding && <BuildSpinner />}
105
+ </div>
106
+ )}
107
+ </div>
108
+
109
+ <div className={styles.footer}>
110
+ <Tooltip delay={300} content={component.id.scope}>
111
+ <div className={styles.scopeBadge} style={{ background: scope?.backgroundIconColor || accent }}>
112
+ {scope?.icon ? (
113
+ <img src={scope.icon} className={styles.scopeBadgeIcon} alt="" />
114
+ ) : (
115
+ <span className={styles.scopeBadgeInitial}>{scopeInitial}</span>
116
+ )}
117
+ </div>
118
+ </Tooltip>
119
+ <span className={styles.name}>{nameLabel}</span>
120
+ {!isBuilding && !isQueued && shortHash && <span className={styles.hash}>{shortHash}</span>}
121
+ {isBuilding && (
122
+ <span className={styles.buildingLabel} style={{ color: accent }}>
123
+ BUILDING
124
+ </span>
125
+ )}
126
+ </div>
127
+ </Link>
128
+ </div>
129
+ );
130
+ }
131
+
132
+ function CardPreview({
133
+ component,
134
+ componentDescriptor,
135
+ status,
136
+ shouldShowPreview,
137
+ }: {
138
+ component: ComponentModel;
139
+ componentDescriptor: ComponentDescriptor;
140
+ status: string;
141
+ shouldShowPreview: boolean;
142
+ }) {
143
+ if (status === 'building') return <BuildingPreview />;
144
+
145
+ return (
146
+ <PreviewPlaceholder
147
+ component={component}
148
+ componentDescriptor={componentDescriptor}
149
+ shouldShowPreview={shouldShowPreview}
150
+ />
151
+ );
152
+ }
@@ -0,0 +1,88 @@
1
+ .header {
2
+ display: flex;
3
+ align-items: center;
4
+ gap: 10px;
5
+ }
6
+
7
+ .dot {
8
+ width: 10px;
9
+ height: 10px;
10
+ border-radius: 3px;
11
+ flex-shrink: 0;
12
+ }
13
+
14
+ .name {
15
+ font-size: 20px;
16
+ font-weight: 600;
17
+ letter-spacing: 0.02em;
18
+ color: var(--on-background-color);
19
+ flex-shrink: 0;
20
+ }
21
+
22
+ .count {
23
+ font-size: 12px;
24
+ color: var(--on-background-low-color);
25
+ flex-shrink: 0;
26
+ }
27
+
28
+ .buildingPill {
29
+ display: inline-flex;
30
+ align-items: center;
31
+ gap: 5px;
32
+ font-size: 10.5px;
33
+ font-weight: 500;
34
+ padding: 1px 8px;
35
+ border-radius: 999px;
36
+ margin-left: 4px;
37
+ flex-shrink: 0;
38
+ }
39
+
40
+ .buildingDot {
41
+ width: 5px;
42
+ height: 5px;
43
+ border-radius: 50%;
44
+ animation: pulse 1.4s ease-in-out infinite;
45
+ }
46
+
47
+ .scopeIconBadge {
48
+ width: 24px;
49
+ height: 24px;
50
+ border-radius: 6px;
51
+ display: flex;
52
+ align-items: center;
53
+ justify-content: center;
54
+ flex-shrink: 0;
55
+ overflow: hidden;
56
+ }
57
+
58
+ .scopeIconImg {
59
+ width: 14px;
60
+ height: 14px;
61
+ object-fit: contain;
62
+ filter: brightness(0) invert(1);
63
+ }
64
+
65
+ .scopeInitial {
66
+ font-family: 'JetBrains Mono', ui-monospace, Menlo, monospace;
67
+ font-size: 11px;
68
+ font-weight: 700;
69
+ color: white;
70
+ line-height: 1;
71
+ }
72
+
73
+ .divider {
74
+ flex: 1;
75
+ height: 1px;
76
+ background: var(--border-medium-color);
77
+ margin-left: 4px;
78
+ }
79
+
80
+ @keyframes pulse {
81
+ 0%,
82
+ 100% {
83
+ opacity: 1;
84
+ }
85
+ 50% {
86
+ opacity: 0.55;
87
+ }
88
+ }
@@ -0,0 +1,72 @@
1
+ import React from 'react';
2
+ import { getComponentStatus } from './filter-utils';
3
+ import type { WorkspaceItem } from './workspace-overview.types';
4
+ import styles from './namespace-header.module.scss';
5
+
6
+ export interface NamespaceHeaderProps {
7
+ namespace: string;
8
+ items: WorkspaceItem[];
9
+ scopeIcon?: string;
10
+ scopeIconColor?: string;
11
+ }
12
+
13
+ export function NamespaceHeader({ namespace, items, scopeIcon, scopeIconColor }: NamespaceHeaderProps) {
14
+ const accent = 'var(--bit-accent-color, #6c5ce7)';
15
+ const tint = 'color-mix(in srgb, var(--bit-accent-color, #6c5ce7) 12%, transparent)';
16
+
17
+ let buildingCount = 0;
18
+ let readyCount = 0;
19
+ for (const item of items) {
20
+ const s = getComponentStatus(item);
21
+ if (s === 'building') buildingCount++;
22
+ if (s === 'built' || s === 'changed') readyCount++;
23
+ }
24
+
25
+ return (
26
+ <header className={styles.header}>
27
+ <HeaderIcon scopeIcon={scopeIcon} scopeIconColor={scopeIconColor} namespace={namespace} accent={accent} />
28
+ <span className={styles.name}>{namespace}</span>
29
+ <span className={styles.count}>
30
+ {readyCount}/{items.length}
31
+ </span>
32
+ {buildingCount > 0 && (
33
+ <span className={styles.buildingPill} style={{ color: accent, background: tint }}>
34
+ <span className={styles.buildingDot} style={{ background: accent }} />
35
+ building
36
+ </span>
37
+ )}
38
+ <div className={styles.divider} />
39
+ </header>
40
+ );
41
+ }
42
+
43
+ function HeaderIcon({
44
+ scopeIcon,
45
+ scopeIconColor,
46
+ namespace,
47
+ accent,
48
+ }: {
49
+ scopeIcon?: string;
50
+ scopeIconColor?: string;
51
+ namespace: string;
52
+ accent: string;
53
+ }) {
54
+ if (scopeIcon) {
55
+ return (
56
+ <div className={styles.scopeIconBadge} style={{ background: scopeIconColor || accent }}>
57
+ <img src={scopeIcon} className={styles.scopeIconImg} alt="" />
58
+ </div>
59
+ );
60
+ }
61
+
62
+ if (scopeIconColor) {
63
+ const initial = namespace.split(/[./]/).pop()?.charAt(0).toUpperCase() || '?';
64
+ return (
65
+ <div className={styles.scopeIconBadge} style={{ background: scopeIconColor }}>
66
+ <span className={styles.scopeInitial}>{initial}</span>
67
+ </div>
68
+ );
69
+ }
70
+
71
+ return <span className={styles.dot} style={{ background: accent }} />;
72
+ }
@@ -58,11 +58,17 @@ export function useWorkspaceAggregation(
58
58
 
59
59
  const sortedScopes = [...map.keys()].sort();
60
60
 
61
- const groups: AggregationGroup[] = sortedScopes.map((sc) => ({
62
- name: sc,
63
- displayName: sc,
64
- items: sortItemsByNamespace(map.get(sc)!),
65
- }));
61
+ const groups: AggregationGroup[] = sortedScopes.map((sc) => {
62
+ const groupItems = map.get(sc)!;
63
+ const sampleScope = groupItems.find((i) => i.scope?.icon)?.scope;
64
+ return {
65
+ name: sc,
66
+ displayName: sc,
67
+ items: sortItemsByNamespace(groupItems),
68
+ scopeIcon: sampleScope?.icon,
69
+ scopeIconColor: sampleScope?.backgroundIconColor,
70
+ };
71
+ });
66
72
 
67
73
  return {
68
74
  groups,
@@ -50,13 +50,15 @@ export function WorkspaceFilterPanel({
50
50
  const activeNsOptions = activeNamespaces.map((v) => ({ value: v }));
51
51
  const activeScopeOptions = activeScopes.map((v) => ({ value: v }));
52
52
 
53
- const applyNs = (opts) => {
54
- const list = opts.map((o) => o.value).filter((v): v is string => typeof v === 'string');
53
+ const applyNs = (opts: readonly { value?: string }[] | { value?: string } | null) => {
54
+ const arr = Array.isArray(opts) ? opts : opts ? [opts] : [];
55
+ const list = arr.map((o) => o.value).filter((v): v is string => typeof v === 'string');
55
56
  onNamespacesChange(list);
56
57
  };
57
58
 
58
- const applyScopes = (opts) => {
59
- const list = opts.map((o) => o.value).filter((v): v is string => typeof v === 'string');
59
+ const applyScopes = (opts: readonly { value?: string }[] | { value?: string } | null) => {
60
+ const arr = Array.isArray(opts) ? opts : opts ? [opts] : [];
61
+ const list = arr.map((o) => o.value).filter((v): v is string => typeof v === 'string');
60
62
  onScopesChange(list);
61
63
  };
62
64
 
@@ -71,8 +73,8 @@ export function WorkspaceFilterPanel({
71
73
  };
72
74
 
73
75
  return (
74
- <div className={styles.filterPanel}>
75
- <div className={styles.leftFilters}>
76
+ <div className={styles.commandBar}>
77
+ <div className={styles.leftCluster}>
76
78
  <BaseFilter
77
79
  id="namespaces"
78
80
  placeholder="Namespaces"
@@ -81,7 +83,6 @@ export function WorkspaceFilterPanel({
81
83
  onChange={applyNs}
82
84
  isSearchable
83
85
  />
84
-
85
86
  <BaseFilter
86
87
  id="scopes"
87
88
  placeholder="Scopes"
@@ -92,9 +93,9 @@ export function WorkspaceFilterPanel({
92
93
  />
93
94
  </div>
94
95
 
95
- <div className={styles.rightAggToggle}>
96
+ <div className={styles.rightCluster}>
96
97
  <ToggleButton
97
- className={styles.toggleBtn}
98
+ className={styles.aggToggle}
98
99
  defaultIndex={currentIndex}
99
100
  onOptionSelect={(idx) => applyAgg(idx)}
100
101
  options={availableAggregations.map((agg) => ({