@teambit/workspace 1.0.1007 → 1.0.1008

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.
@@ -0,0 +1,124 @@
1
+ import React, { useState } from 'react';
2
+ import { Icon } from '@teambit/evangelist.elements.icon';
3
+ import { useWorkspaceMode } from '@teambit/workspace.ui.use-workspace-mode';
4
+ import { HopeAiIcon } from './hope-ai-icon';
5
+ import styles from './workspace-blank-state.module.scss';
6
+
7
+ const DISCORD_URL = 'https://discord.bit.cloud/';
8
+
9
+ /**
10
+ * Empty workspace state, shown when the workspace has no components.
11
+ * Replaces the legacy `@teambit/workspace.ui.empty-workspace` for the Hope flow.
12
+ *
13
+ * The default state guides the user through the Bit CLI in their terminal. In
14
+ * minimal mode (the embedded Hope experience) we additionally surface the
15
+ * "prompt Hope in the chat" CTA.
16
+ */
17
+ export function WorkspaceBlankState() {
18
+ const { isMinimal } = useWorkspaceMode();
19
+
20
+ return (
21
+ <div className={styles.container}>
22
+ <div className={styles.vignette} aria-hidden />
23
+
24
+ <div className={styles.body}>
25
+ <h1 className={styles.headline}>
26
+ Your workspace is <em>ready</em> for its first component.
27
+ </h1>
28
+
29
+ <p className={styles.sub}>Components will appear here as they're built.</p>
30
+
31
+ {/* Primary — prompt Hope in the chat (minimal mode only) */}
32
+ {isMinimal && (
33
+ <>
34
+ <div className={styles.hopeCallout}>
35
+ <HopeAiIcon size={32} className={styles.hopeIcon} />
36
+ <div className={styles.hopeText}>
37
+ <div className={styles.hopeTitle}>Prompt Hope in the chat</div>
38
+ <div className={styles.hopeHelp}>
39
+ Describe what you want to build, from one prompt to a whole company.
40
+ </div>
41
+ </div>
42
+ </div>
43
+
44
+ {/* OR separator */}
45
+ <div className={styles.sep}>
46
+ <div className={styles.sepLine} />
47
+ <span className={styles.sepLabel}>or do it yourself</span>
48
+ <div className={styles.sepLine} />
49
+ </div>
50
+ </>
51
+ )}
52
+
53
+ {/* DIY CLI options */}
54
+ <div className={styles.diyGrid}>
55
+ <DiyRow title="Create" body="Scaffold a new component." cmd="bit create react button" />
56
+ <DiyRow title="Import" body="Bring one from another scope." cmd="bit import org.scope/comp" />
57
+ </div>
58
+
59
+ <div className={styles.docsLinks}>
60
+ <a href="https://bit.dev/docs" className={styles.link} target="_blank" rel="noopener noreferrer">
61
+ CLI docs ↗
62
+ </a>
63
+ <a href="https://bit.cloud/docs" className={styles.link} target="_blank" rel="noopener noreferrer">
64
+ Cloud docs ↗
65
+ </a>
66
+ </div>
67
+ </div>
68
+
69
+ <div className={styles.bottom}>
70
+ <IconLink
71
+ href="https://github.com/teambit/bit"
72
+ src="https://static.bit.dev/harmony/github.svg"
73
+ label="Bit on GitHub"
74
+ />
75
+ <a
76
+ href={DISCORD_URL}
77
+ className={styles.iconLink}
78
+ target="_blank"
79
+ rel="noopener noreferrer"
80
+ aria-label="Bit community on Discord"
81
+ >
82
+ <Icon of="discord" className={styles.discordIcon} />
83
+ </a>
84
+ </div>
85
+ </div>
86
+ );
87
+ }
88
+
89
+ function IconLink({ href, src, label }: { href: string; src: string; label: string }) {
90
+ return (
91
+ <a href={href} className={styles.iconLink} target="_blank" rel="noopener noreferrer" aria-label={label}>
92
+ <img src={src} alt={label} className={styles.logo} />
93
+ </a>
94
+ );
95
+ }
96
+
97
+ function DiyRow({ title, body, cmd }: { title: string; body: string; cmd: string }) {
98
+ const [copied, setCopied] = useState(false);
99
+ const onCopy = (e: React.MouseEvent) => {
100
+ e.stopPropagation();
101
+ if (typeof navigator !== 'undefined' && navigator.clipboard) {
102
+ navigator.clipboard.writeText(cmd).catch(() => undefined);
103
+ }
104
+ setCopied(true);
105
+ setTimeout(() => setCopied(false), 1400);
106
+ };
107
+ return (
108
+ <div className={styles.diyRow}>
109
+ <div>
110
+ <div className={styles.diyTitle}>{title}</div>
111
+ <div className={styles.diyBody}>{body}</div>
112
+ </div>
113
+ <div className={styles.diyCmdRow}>
114
+ <div className={styles.diyCmd}>
115
+ <span className={styles.diyPrompt}>$</span>
116
+ <span className={styles.diyCmdText}>{cmd}</span>
117
+ </div>
118
+ <button type="button" onClick={onCopy} className={styles.diyCopy}>
119
+ {copied ? 'Copied' : 'Copy'}
120
+ </button>
121
+ </div>
122
+ </div>
123
+ );
124
+ }
@@ -1,5 +1,7 @@
1
1
  import React, { useContext, useMemo } from 'react';
2
2
  import { ComponentGrid } from '@teambit/explorer.ui.gallery.component-grid';
3
+ // Legacy empty state — kept around as a fallback while the new blank state rolls out.
4
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
3
5
  import { EmptyWorkspace } from '@teambit/workspace.ui.empty-workspace';
4
6
  import compact from 'lodash.compact';
5
7
  import { ScopeID } from '@teambit/scopes.scope-id';
@@ -12,14 +14,13 @@ import { NamespaceHeader } from './namespace-header';
12
14
  import { HopeComponentCard } from './hope-component-card';
13
15
  import type { AggregationType } from './workspace-overview.types';
14
16
  import { WorkspaceFilterPanel } from './workspace-filter-panel';
17
+ import { WorkspaceBlankState } from './workspace-blank-state';
15
18
  import styles from './workspace-overview.module.scss';
16
19
 
17
20
  export function WorkspaceOverview() {
18
21
  const workspace = useContext(WorkspaceContext);
19
22
  const { components, componentDescriptors } = workspace;
20
23
 
21
- if (!components.length) return <EmptyWorkspace name={workspace.name} />;
22
-
23
24
  const { isMinimal } = useWorkspaceMode();
24
25
  const uniqueScopes = [...new Set(components.map((c) => c.id.scope))];
25
26
  const { cloudScopes } = useCloudScopes(uniqueScopes);
@@ -65,6 +66,8 @@ export function WorkspaceOverview() {
65
66
  filters
66
67
  );
67
68
 
69
+ if (!components.length) return <WorkspaceBlankState />;
70
+
68
71
  return (
69
72
  <div className={styles.container}>
70
73
  <WorkspaceFilterPanel
@@ -79,7 +82,7 @@ export function WorkspaceOverview() {
79
82
  />
80
83
 
81
84
  <div className={styles.content}>
82
- {filteredCount === 0 && <EmptyWorkspace name={workspace.name} />}
85
+ {filteredCount === 0 && <WorkspaceBlankState />}
83
86
 
84
87
  {groups.map((group) => (
85
88
  <section key={group.name} className={styles.section}>