From d1a7188b9ea185b3511bd3e7dc6ea992d7ae5d2c Mon Sep 17 00:00:00 2001 From: Luv Kapur Date: Fri, 22 May 2026 13:54:12 -0400 Subject: [PATCH] new workspace empty state --- .../workspace-blank-state.module.scss | 303 ++++++++++++++++++ .../workspace-blank-state.tsx | 120 +++++++ .../workspace-overview/workspace-overview.tsx | 7 +- workspace.jsonc | 1 + 4 files changed, 429 insertions(+), 2 deletions(-) create mode 100644 scopes/workspace/workspace/ui/workspace/workspace-overview/workspace-blank-state.module.scss create mode 100644 scopes/workspace/workspace/ui/workspace/workspace-overview/workspace-blank-state.tsx diff --git a/scopes/workspace/workspace/ui/workspace/workspace-overview/workspace-blank-state.module.scss b/scopes/workspace/workspace/ui/workspace/workspace-overview/workspace-blank-state.module.scss new file mode 100644 index 000000000000..6be3ef075d09 --- /dev/null +++ b/scopes/workspace/workspace/ui/workspace/workspace-overview/workspace-blank-state.module.scss @@ -0,0 +1,303 @@ +.container { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + background: var(--background-color); + overflow-y: auto; + position: relative; +} + +.vignette { + position: absolute; + inset: 0; + pointer-events: none; + background: radial-gradient(ellipse at 50% 22%, var(--surface01-color, #f7f7f7) 0%, var(--background-color) 60%); +} + +/* Centered in the space above the footer via auto margins. */ +.body { + position: relative; + z-index: 1; + box-sizing: border-box; + width: 100%; + max-width: 720px; + margin: auto; + padding: 48px 32px; + text-align: center; +} + +.headline { + font-family: 'Instrument Serif', Georgia, serif; + margin: 0; + font-size: 44px; + line-height: 1.1; + font-weight: 400; + color: var(--on-background-high-color); + letter-spacing: -0.02em; + text-wrap: balance; + + em { + color: var(--bit-accent-color, #6c5ce7); + font-style: italic; + } +} + +.sub { + margin: 16px auto 40px; + max-width: 460px; + font-size: 14px; + line-height: 1.55; + color: var(--on-background-medium-color); +} + +/* ---- Primary CTA — Hope callout ---- */ + +.hopeCallout { + display: flex; + align-items: center; + gap: 16px; + padding: 20px 24px; + background: var(--primary-surface-color, #f6f5fe); + border: 1.5px solid var(--border-primary-color, #6c5ce7); + border-radius: 14px; + box-shadow: + 0 1px 2px rgba(93, 72, 255, 0.06), + 0 18px 40px -18px rgba(93, 72, 255, 0.4); + text-align: left; +} + +.hopeIcon { + flex-shrink: 0; +} + +.hopeText { + flex: 1; +} + +.hopeTitle { + font-size: 16px; + font-weight: 600; + color: var(--on-background-high-color); + letter-spacing: -0.01em; + margin-bottom: 3px; +} + +.hopeHelp { + font-size: 13px; + color: var(--on-background-medium-color); + line-height: 1.5; +} + +/* ---- Separator ---- */ + +.sep { + display: flex; + align-items: center; + gap: 14px; + margin: 32px auto 20px; + max-width: 340px; +} + +.sepLine { + flex: 1; + height: 1px; + background: var(--border-medium-color); +} + +.sepLabel { + font-family: 'JetBrains Mono', ui-monospace, Menlo, monospace; + font-size: 11px; + color: var(--on-background-low-color); + letter-spacing: 0.14em; + text-transform: uppercase; +} + +/* ---- DIY rows ---- */ + +.diyGrid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 16px; +} + +.diyRow { + display: flex; + flex-direction: column; + gap: 14px; + padding: 16px 18px; + background: var(--surface-color); + border: 1px solid var(--border-medium-color); + border-radius: 12px; + text-align: left; +} + +.diyTitle { + font-size: 14px; + font-weight: 600; + color: var(--on-background-high-color); +} + +.diyBody { + font-size: 13px; + color: var(--on-background-medium-color); + line-height: 1.45; + margin-top: 2px; +} + +.diyCmdRow { + display: flex; + align-items: center; + gap: 8px; +} + +.diyCmd { + flex: 1; + display: flex; + align-items: center; + gap: 8px; + padding: 8px 10px; + background: var(--surface01-color, #f7f7f7); + border: 1px solid var(--border-medium-color); + border-radius: 8px; + font-family: 'JetBrains Mono', ui-monospace, Menlo, monospace; + font-size: 13px; + color: var(--on-background-color); + min-width: 0; +} + +.diyPrompt { + color: var(--on-background-low-color); +} + +.diyCmdText { + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.diyCopy { + height: 30px; + padding: 0 12px; + background: transparent; + border: 1px solid var(--border-medium-color); + border-radius: 8px; + font-size: 12px; + font-weight: 500; + color: var(--on-background-medium-color); + cursor: pointer; + flex-shrink: 0; + transition: background 0.12s ease; + + &:hover { + background: var(--surface01-color, #f7f7f7); + } +} + +/* ---- Docs links ---- */ + +.docsLinks { + margin-top: 32px; + display: flex; + justify-content: center; + gap: 28px; +} + +.link { + color: var(--bit-accent-color, #6c5ce7); + text-decoration: none; + font-weight: 500; + font-size: 13px; +} + +/* ---- Contact CTA band ---- */ + +.contactBand { + box-sizing: border-box; + position: relative; + z-index: 1; + display: flex; + align-items: center; + justify-content: space-between; + gap: 24px; + width: calc(100% - 48px); + max-width: 720px; + margin: 24px auto 16px; + padding: 18px 24px; + border: 1px solid var(--border-primary-color, #6c5ce7); + border-radius: 14px; + background: var(--primary-surface-color, #f6f5fe); + text-decoration: none; + transition: box-shadow 0.14s ease; + + &:hover { + box-shadow: 0 12px 30px -18px rgba(93, 72, 255, 0.5); + } +} + +.contactText { + display: flex; + flex-direction: column; + text-align: left; +} + +.contactTitle { + font-size: 15px; + font-weight: 600; + color: var(--on-background-high-color); + letter-spacing: -0.01em; +} + +.contactSub { + margin-top: 2px; + font-size: 13px; + line-height: 1.45; + color: var(--on-background-medium-color); +} + +.contactBtn { + flex-shrink: 0; + display: inline-flex; + align-items: center; + height: 36px; + padding: 0 18px; + border-radius: 9px; + background: var(--bit-accent-color, #6c5ce7); + color: #fff; + font-size: 13px; + font-weight: 600; + white-space: nowrap; +} + +/* ---- Footer ---- */ + +.bottom { + flex-shrink: 0; + position: relative; + z-index: 1; + padding: 20px 24px; + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + border-top: 1px solid var(--border-medium-color); + background: var(--surface-color); +} + +.iconLink { + display: inline-flex; + padding: 8px; + border-radius: 10px; + transition: background 0.14s ease; + + &:hover { + background: var(--surface-hover-color, rgba(120, 120, 140, 0.12)); + } +} + +.logo { + width: 28px; + height: 28px; + display: block; +} diff --git a/scopes/workspace/workspace/ui/workspace/workspace-overview/workspace-blank-state.tsx b/scopes/workspace/workspace/ui/workspace/workspace-overview/workspace-blank-state.tsx new file mode 100644 index 000000000000..83726a66d10d --- /dev/null +++ b/scopes/workspace/workspace/ui/workspace/workspace-overview/workspace-blank-state.tsx @@ -0,0 +1,120 @@ +import React, { useState } from 'react'; +import { HopeAiIcon } from '@teambit/hope.design.hope-icon'; +import styles from './workspace-blank-state.module.scss'; + +/** + * Empty workspace state, shown when the workspace has no components. + * Replaces the legacy `@teambit/workspace.ui.empty-workspace` for the Hope flow. + * + * The primary CTA invites the user to prompt Hope in the chat. The secondary + * "do it yourself" path uses the Bit CLI in the user's terminal. + */ +export function WorkspaceBlankState() { + return ( +
+
+ +
+

+ Your workspace is ready for its first component. +

+ +

Components will appear here as they're built.

+ + {/* Primary — prompt Hope in the chat */} +
+ +
+
Prompt Hope in the chat
+
+ Describe what you want to build, from a single component to a whole design system. +
+
+
+ + {/* OR separator */} +
+
+ or do it yourself +
+
+ + {/* Secondary — DIY CLI options */} +
+ + +
+ + +
+ + {/* Highlighted CTA — talk to a Bit Cloud expert */} + + + Talk to a Bit Cloud expert + + See how Bit accelerates your team, from first product to production. + + + Contact us → + + +
+ + +
+
+ ); +} + +function IconLink({ href, src, label }: { href: string; src: string; label: string }) { + return ( + + {label} + + ); +} + +function DiyRow({ title, body, cmd }: { title: string; body: string; cmd: string }) { + const [copied, setCopied] = useState(false); + const onCopy = (e: React.MouseEvent) => { + e.stopPropagation(); + if (typeof navigator !== 'undefined' && navigator.clipboard) { + navigator.clipboard.writeText(cmd).catch(() => undefined); + } + setCopied(true); + setTimeout(() => setCopied(false), 1400); + }; + return ( +
+
+
{title}
+
{body}
+
+
+
+ $ + {cmd} +
+ +
+
+ ); +} diff --git a/scopes/workspace/workspace/ui/workspace/workspace-overview/workspace-overview.tsx b/scopes/workspace/workspace/ui/workspace/workspace-overview/workspace-overview.tsx index f60a151c0a99..34c60a3a5ce1 100644 --- a/scopes/workspace/workspace/ui/workspace/workspace-overview/workspace-overview.tsx +++ b/scopes/workspace/workspace/ui/workspace/workspace-overview/workspace-overview.tsx @@ -1,5 +1,7 @@ import React, { useContext, useMemo } from 'react'; import { ComponentGrid } from '@teambit/explorer.ui.gallery.component-grid'; +// Legacy empty state — kept around as a fallback while the new blank state rolls out. +// eslint-disable-next-line @typescript-eslint/no-unused-vars import { EmptyWorkspace } from '@teambit/workspace.ui.empty-workspace'; import compact from 'lodash.compact'; import { ScopeID } from '@teambit/scopes.scope-id'; @@ -12,13 +14,14 @@ import { NamespaceHeader } from './namespace-header'; import { HopeComponentCard } from './hope-component-card'; import type { AggregationType } from './workspace-overview.types'; import { WorkspaceFilterPanel } from './workspace-filter-panel'; +import { WorkspaceBlankState } from './workspace-blank-state'; import styles from './workspace-overview.module.scss'; export function WorkspaceOverview() { const workspace = useContext(WorkspaceContext); const { components, componentDescriptors } = workspace; - if (!components.length) return ; + if (!components.length) return ; const { isMinimal } = useWorkspaceMode(); const uniqueScopes = [...new Set(components.map((c) => c.id.scope))]; @@ -79,7 +82,7 @@ export function WorkspaceOverview() { />
- {filteredCount === 0 && } + {filteredCount === 0 && } {groups.map((group) => (
diff --git a/workspace.jsonc b/workspace.jsonc index 81815fbd696a..43842b9557fe 100644 --- a/workspace.jsonc +++ b/workspace.jsonc @@ -285,6 +285,7 @@ "@teambit/gitconfig": "2.0.10", "@teambit/graph.cleargraph": "0.0.11", "@teambit/harmony": "0.4.7", + "@teambit/hope.design.hope-icon": "^0.0.16", "@teambit/html.modules.inject-html-element": "0.0.6", "@teambit/lane-id": "~0.0.312", "@teambit/lanes.ui.compare.lane-compare": "^0.0.206",