From 300cbc7af89d2ad1a9667937558a882f54a389ab Mon Sep 17 00:00:00 2001 From: Luv Kapur Date: Fri, 1 May 2026 17:04:17 -0400 Subject: [PATCH 01/13] feat(workspace-overview): add Density/ComponentStatus types and namespace hue helpers Co-Authored-By: Claude Opus 4.6 --- .../workspace-overview/namespace-hues.ts | 30 +++++++++++++++++++ .../workspace-overview.types.ts | 4 +++ 2 files changed, 34 insertions(+) create mode 100644 scopes/workspace/workspace/ui/workspace/workspace-overview/namespace-hues.ts diff --git a/scopes/workspace/workspace/ui/workspace/workspace-overview/namespace-hues.ts b/scopes/workspace/workspace/ui/workspace/workspace-overview/namespace-hues.ts new file mode 100644 index 000000000000..a55f0bfdee38 --- /dev/null +++ b/scopes/workspace/workspace/ui/workspace/workspace-overview/namespace-hues.ts @@ -0,0 +1,30 @@ +const NS_HUES: Record = { + actions: 264, + content: 200, + surfaces: 162, + feedback: 38, + inputs: 220, + charts: 142, + navigation: 280, + envs: 250, +}; + +function hashToHue(ns: string): number { + let hash = 0; + for (let i = 0; i < ns.length; i++) { + hash = ((hash << 5) - hash + ns.charCodeAt(i)) | 0; + } + return ((hash % 360) + 360) % 360; +} + +export function getHue(ns: string): number { + return NS_HUES[ns] ?? hashToHue(ns); +} + +export function getAccent(ns: string): string { + return `oklch(58% 0.18 ${getHue(ns)})`; +} + +export function getTint(ns: string): string { + return `oklch(96% 0.04 ${getHue(ns)})`; +} diff --git a/scopes/workspace/workspace/ui/workspace/workspace-overview/workspace-overview.types.ts b/scopes/workspace/workspace/ui/workspace/workspace-overview/workspace-overview.types.ts index 1630930a4978..fa149c5d5b8f 100644 --- a/scopes/workspace/workspace/ui/workspace/workspace-overview/workspace-overview.types.ts +++ b/scopes/workspace/workspace/ui/workspace/workspace-overview/workspace-overview.types.ts @@ -10,6 +10,10 @@ export interface WorkspaceItem { export type AggregationType = 'namespaces' | 'scopes' | 'none'; +export type Density = 'compact' | 'comfy'; + +export type ComponentStatus = 'built' | 'changed' | 'building' | 'queued'; + export interface AggregationGroup { name: string; displayName: string; From aa1ac7d7cdfd5359f2d95bac3e11093f41aaee5c Mon Sep 17 00:00:00 2001 From: Luv Kapur Date: Fri, 1 May 2026 17:04:43 -0400 Subject: [PATCH 02/13] feat(workspace-overview): add status filtering to ActiveFilters Co-Authored-By: Claude Opus 4.6 --- .../workspace-overview/filter-utils.ts | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/scopes/workspace/workspace/ui/workspace/workspace-overview/filter-utils.ts b/scopes/workspace/workspace/ui/workspace/workspace-overview/filter-utils.ts index 7508bee65061..5a5d3615a260 100644 --- a/scopes/workspace/workspace/ui/workspace/workspace-overview/filter-utils.ts +++ b/scopes/workspace/workspace/ui/workspace/workspace-overview/filter-utils.ts @@ -1,17 +1,30 @@ -import type { WorkspaceItem } from './workspace-overview.types'; +import type { WorkspaceItem, ComponentStatus } from './workspace-overview.types'; export interface ActiveFilters { namespaces: string[]; scopes: string[]; + statuses: Set; } +export const ALL_STATUSES: ComponentStatus[] = ['built', 'changed', 'building', 'queued']; + export function parseActiveFilters(search: URLSearchParams): ActiveFilters { return { namespaces: (search.get('ns') || '').split(',').filter(Boolean), scopes: (search.get('scopes') || '').split(',').filter(Boolean), + statuses: new Set(ALL_STATUSES), }; } +export function getComponentStatus(item: WorkspaceItem): ComponentStatus { + const buildStatus = (item.component as any).buildStatus; + const status = (item.component as any).status; + if (buildStatus === 'pending') return 'queued'; + if (status?.modifyInfo?.hasModifiedFiles || status?.modifyInfo?.hasModifiedDependencies) return 'changed'; + if (buildStatus === 'building') return 'building'; + return 'built'; +} + export function filterItems(items: WorkspaceItem[], filters: ActiveFilters): WorkspaceItem[] { return items.filter((item) => { const ns = item.component.id.namespace || '/'; @@ -19,6 +32,10 @@ export function filterItems(items: WorkspaceItem[], filters: ActiveFilters): Wor if (filters.namespaces.length && !filters.namespaces.includes(ns)) return false; if (filters.scopes.length && !filters.scopes.includes(sc)) return false; + if (filters.statuses.size > 0 && filters.statuses.size < ALL_STATUSES.length) { + const componentStatus = getComponentStatus(item); + if (!filters.statuses.has(componentStatus)) return false; + } return true; }); From 55189efc062de1107520270cb76087d7dcb0fd67 Mon Sep 17 00:00:00 2001 From: Luv Kapur Date: Fri, 1 May 2026 17:06:40 -0400 Subject: [PATCH 03/13] feat(workspace-overview): add EmptyFilters component Co-Authored-By: Claude Opus 4.6 --- .../empty-filters.module.scss | 35 +++++++++++++++++++ .../workspace-overview/empty-filters.tsx | 18 ++++++++++ 2 files changed, 53 insertions(+) create mode 100644 scopes/workspace/workspace/ui/workspace/workspace-overview/empty-filters.module.scss create mode 100644 scopes/workspace/workspace/ui/workspace/workspace-overview/empty-filters.tsx diff --git a/scopes/workspace/workspace/ui/workspace/workspace-overview/empty-filters.module.scss b/scopes/workspace/workspace/ui/workspace/workspace-overview/empty-filters.module.scss new file mode 100644 index 000000000000..13ce4fc18f84 --- /dev/null +++ b/scopes/workspace/workspace/ui/workspace/workspace-overview/empty-filters.module.scss @@ -0,0 +1,35 @@ +.container { + text-align: center; + padding: 80px 20px; + color: var(--on-background-medium-color); +} + +.headline { + font-family: 'Instrument Serif', Georgia, serif; + font-weight: 400; + font-size: 28px; + color: var(--on-background-color); + margin-bottom: 10px; + letter-spacing: -0.01em; +} + +.body { + font-size: 13px; + margin-bottom: 18px; +} + +.clearButton { + height: 34px; + padding: 0 18px; + border-radius: 999px; + background: var(--surface-primary-color); + border: none; + font-size: 13px; + font-weight: 500; + cursor: pointer; + color: white; +} + +.clearButton:hover { + background: var(--surface-primary-hover-color); +} diff --git a/scopes/workspace/workspace/ui/workspace/workspace-overview/empty-filters.tsx b/scopes/workspace/workspace/ui/workspace/workspace-overview/empty-filters.tsx new file mode 100644 index 000000000000..4a9b5e404eab --- /dev/null +++ b/scopes/workspace/workspace/ui/workspace/workspace-overview/empty-filters.tsx @@ -0,0 +1,18 @@ +import React from 'react'; +import styles from './empty-filters.module.scss'; + +export interface EmptyFiltersProps { + onClear: () => void; +} + +export function EmptyFilters({ onClear }: EmptyFiltersProps) { + return ( +
+
Nothing matches.
+
Try clearing namespace, scope, or status filters.
+ +
+ ); +} From 280581f920bdb610fa761c1a53313cf4f33c226f Mon Sep 17 00:00:00 2001 From: Luv Kapur Date: Fri, 1 May 2026 17:06:46 -0400 Subject: [PATCH 04/13] feat(workspace-overview): add NamespaceHeader component Co-Authored-By: Claude Opus 4.6 --- .../namespace-header.module.scss | 66 +++++++++++++++++++ .../workspace-overview/namespace-header.tsx | 40 +++++++++++ 2 files changed, 106 insertions(+) create mode 100644 scopes/workspace/workspace/ui/workspace/workspace-overview/namespace-header.module.scss create mode 100644 scopes/workspace/workspace/ui/workspace/workspace-overview/namespace-header.tsx diff --git a/scopes/workspace/workspace/ui/workspace/workspace-overview/namespace-header.module.scss b/scopes/workspace/workspace/ui/workspace/workspace-overview/namespace-header.module.scss new file mode 100644 index 000000000000..81b424d47fd5 --- /dev/null +++ b/scopes/workspace/workspace/ui/workspace/workspace-overview/namespace-header.module.scss @@ -0,0 +1,66 @@ +.header { + display: flex; + align-items: center; + gap: 10px; + margin-bottom: 12px; +} + +.dot { + width: 8px; + height: 8px; + border-radius: 2px; + flex-shrink: 0; +} + +.name { + font-family: 'JetBrains Mono', ui-monospace, Menlo, monospace; + font-size: 11px; + font-weight: 600; + letter-spacing: 0.12em; + text-transform: uppercase; + color: var(--on-background-color); + flex-shrink: 0; +} + +.count { + font-family: 'JetBrains Mono', ui-monospace, Menlo, monospace; + font-size: 11px; + color: var(--on-background-low-color); + flex-shrink: 0; +} + +.buildingPill { + display: inline-flex; + align-items: center; + gap: 5px; + font-size: 10.5px; + font-weight: 500; + padding: 1px 8px; + border-radius: 999px; + margin-left: 4px; + flex-shrink: 0; +} + +.buildingDot { + width: 5px; + height: 5px; + border-radius: 50%; + animation: pulse 1.4s ease-in-out infinite; +} + +.divider { + flex: 1; + height: 1px; + background: var(--border-medium-color); + margin-left: 4px; +} + +@keyframes pulse { + 0%, + 100% { + opacity: 1; + } + 50% { + opacity: 0.55; + } +} diff --git a/scopes/workspace/workspace/ui/workspace/workspace-overview/namespace-header.tsx b/scopes/workspace/workspace/ui/workspace/workspace-overview/namespace-header.tsx new file mode 100644 index 000000000000..9f3d37b902e9 --- /dev/null +++ b/scopes/workspace/workspace/ui/workspace/workspace-overview/namespace-header.tsx @@ -0,0 +1,40 @@ +import React from 'react'; +import { getAccent, getTint } from './namespace-hues'; +import { getComponentStatus } from './filter-utils'; +import type { WorkspaceItem } from './workspace-overview.types'; +import styles from './namespace-header.module.scss'; + +export interface NamespaceHeaderProps { + namespace: string; + items: WorkspaceItem[]; +} + +export function NamespaceHeader({ namespace, items }: NamespaceHeaderProps) { + const accent = getAccent(namespace); + const tint = getTint(namespace); + + let buildingCount = 0; + let readyCount = 0; + for (const item of items) { + const s = getComponentStatus(item); + if (s === 'building') buildingCount++; + if (s === 'built' || s === 'changed') readyCount++; + } + + return ( +
+ + {namespace} + + {readyCount}/{items.length} + + {buildingCount > 0 && ( + + + building + + )} +
+
+ ); +} From 346ac80801da958374cff4998b5dff22bb5bfc47 Mon Sep 17 00:00:00 2001 From: Luv Kapur Date: Fri, 1 May 2026 17:06:48 -0400 Subject: [PATCH 05/13] feat(workspace-overview): add StatusPills component Co-Authored-By: Claude Opus 4.6 --- .../card-overlays.module.scss | 72 +++++++++++++++++++ .../workspace-overview/card-overlays.tsx | 64 +++++++++++++++++ .../density-toggle.module.scss | 30 ++++++++ .../workspace-overview/density-toggle.tsx | 47 ++++++++++++ .../status-pills.module.scss | 57 +++++++++++++++ .../workspace-overview/status-pills.tsx | 55 ++++++++++++++ 6 files changed, 325 insertions(+) create mode 100644 scopes/workspace/workspace/ui/workspace/workspace-overview/card-overlays.module.scss create mode 100644 scopes/workspace/workspace/ui/workspace/workspace-overview/card-overlays.tsx create mode 100644 scopes/workspace/workspace/ui/workspace/workspace-overview/density-toggle.module.scss create mode 100644 scopes/workspace/workspace/ui/workspace/workspace-overview/density-toggle.tsx create mode 100644 scopes/workspace/workspace/ui/workspace/workspace-overview/status-pills.module.scss create mode 100644 scopes/workspace/workspace/ui/workspace/workspace-overview/status-pills.tsx diff --git a/scopes/workspace/workspace/ui/workspace/workspace-overview/card-overlays.module.scss b/scopes/workspace/workspace/ui/workspace/workspace-overview/card-overlays.module.scss new file mode 100644 index 000000000000..3a2f399835f4 --- /dev/null +++ b/scopes/workspace/workspace/ui/workspace/workspace-overview/card-overlays.module.scss @@ -0,0 +1,72 @@ +.changedPill { + display: inline-flex; + align-items: center; + gap: 5px; + font-size: 10px; + padding: 3px 8px; + border-radius: 999px; + background: var(--warning-surface-color); + color: var(--warning-color); + font-weight: 600; + letter-spacing: 0.02em; +} + +.changedDot { + width: 5px; + height: 5px; + border-radius: 50%; + background: var(--warning-color); +} + +.spinnerBadge { + width: 22px; + height: 22px; + border-radius: 50%; + background: var(--surface-color); + display: inline-flex; + align-items: center; + justify-content: center; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); +} + +.spinnerArc { + transform-origin: center; + animation: spinSlow 0.9s linear infinite; +} + +.buildingPreview { + position: relative; + height: 100%; +} + +.dotsPattern { + position: absolute; + inset: 0; +} + +.buildingPlaceholder { + position: absolute; + inset: 20% 14%; + border-radius: 10px; + background: rgba(255, 255, 255, 0.55); + backdrop-filter: blur(2px); + display: flex; + flex-direction: column; + justify-content: center; + padding: 0 16px; + gap: 7px; +} + +.queuedPreview { + position: absolute; + inset: 0; + display: flex; + align-items: center; + justify-content: center; +} + +@keyframes spinSlow { + to { + transform: rotate(360deg); + } +} diff --git a/scopes/workspace/workspace/ui/workspace/workspace-overview/card-overlays.tsx b/scopes/workspace/workspace/ui/workspace/workspace-overview/card-overlays.tsx new file mode 100644 index 000000000000..160a297d7164 --- /dev/null +++ b/scopes/workspace/workspace/ui/workspace/workspace-overview/card-overlays.tsx @@ -0,0 +1,64 @@ +import React from 'react'; +import styles from './card-overlays.module.scss'; + +export function ChangedPill() { + return ( + + + changed + + ); +} + +export function BuildSpinner({ accent }: { accent: string }) { + return ( +
+ + + + +
+ ); +} + +export function BuildingPreview({ accent }: { accent: string }) { + const patternId = `dots-${accent.replace(/[^a-z0-9]/g, '')}`; + return ( +
+ + + + + + + + +
+
+
+
+
+
+ ); +} + +export function QueuedPreview({ accent }: { accent: string }) { + return ( +
+ + + + +
+ ); +} diff --git a/scopes/workspace/workspace/ui/workspace/workspace-overview/density-toggle.module.scss b/scopes/workspace/workspace/ui/workspace/workspace-overview/density-toggle.module.scss new file mode 100644 index 000000000000..776d0b4a9021 --- /dev/null +++ b/scopes/workspace/workspace/ui/workspace/workspace-overview/density-toggle.module.scss @@ -0,0 +1,30 @@ +.track { + display: inline-flex; + background: var(--surface01-color); + border-radius: 8px; + padding: 2px; + height: 32px; + border: 1px solid var(--border-medium-color); +} + +.option { + width: 30px; + height: 26px; + border-radius: 6px; + background: transparent; + color: var(--on-background-low-color); + border: none; + cursor: pointer; + display: inline-flex; + align-items: center; + justify-content: center; + box-shadow: none; + transition: all 0.15s ease; +} + +.optionActive { + composes: option; + background: var(--surface-color); + color: var(--on-background-color); + box-shadow: 0 1px 2px rgba(20, 0, 104, 0.06); +} diff --git a/scopes/workspace/workspace/ui/workspace/workspace-overview/density-toggle.tsx b/scopes/workspace/workspace/ui/workspace/workspace-overview/density-toggle.tsx new file mode 100644 index 000000000000..6be5b4acceff --- /dev/null +++ b/scopes/workspace/workspace/ui/workspace/workspace-overview/density-toggle.tsx @@ -0,0 +1,47 @@ +import React from 'react'; +import type { Density } from './workspace-overview.types'; +import styles from './density-toggle.module.scss'; + +export interface DensityToggleProps { + value: Density; + onChange: (density: Density) => void; +} + +const CompactIcon = () => ( + + + + + + +); + +const ComfyIcon = () => ( + + + + +); + +const OPTIONS: { value: Density; icon: React.ReactNode }[] = [ + { value: 'compact', icon: }, + { value: 'comfy', icon: }, +]; + +export function DensityToggle({ value, onChange }: DensityToggleProps) { + return ( +
+ {OPTIONS.map((o) => ( + + ))} +
+ ); +} diff --git a/scopes/workspace/workspace/ui/workspace/workspace-overview/status-pills.module.scss b/scopes/workspace/workspace/ui/workspace/workspace-overview/status-pills.module.scss new file mode 100644 index 000000000000..1413dde1c837 --- /dev/null +++ b/scopes/workspace/workspace/ui/workspace/workspace-overview/status-pills.module.scss @@ -0,0 +1,57 @@ +.pill { + display: inline-flex; + align-items: center; + gap: 7px; + height: 30px; + padding: 0 12px; + border-radius: 999px; + border: none; + font-size: 12px; + font-weight: 500; + cursor: pointer; + transition: all 0.15s ease; + background: transparent; + color: var(--on-background-low-color); +} + +.active { + background: var(--surface01-color); + color: var(--on-background-color); +} + +.dot { + width: 7px; + height: 7px; + border-radius: 50%; + background: var(--border-high-color); + flex-shrink: 0; +} + +.dotActive { + composes: dot; +} + +.dotBuilding { + composes: dotActive; + animation: pulse 1.4s ease-in-out infinite; +} + +.pulsingRing { + box-shadow: 0 0 0 3px rgba(93, 72, 255, 0.2); +} + +.count { + font-size: 10px; + opacity: 0.6; + font-family: 'JetBrains Mono', ui-monospace, Menlo, monospace; +} + +@keyframes pulse { + 0%, + 100% { + opacity: 1; + } + 50% { + opacity: 0.55; + } +} diff --git a/scopes/workspace/workspace/ui/workspace/workspace-overview/status-pills.tsx b/scopes/workspace/workspace/ui/workspace/workspace-overview/status-pills.tsx new file mode 100644 index 000000000000..a7b3f891969c --- /dev/null +++ b/scopes/workspace/workspace/ui/workspace/workspace-overview/status-pills.tsx @@ -0,0 +1,55 @@ +import React from 'react'; +import classNames from 'classnames'; +import type { ComponentStatus } from './workspace-overview.types'; +import styles from './status-pills.module.scss'; + +const STATUS_META: Record = { + built: { label: 'Built', dotColor: 'var(--positive-color)' }, + changed: { label: 'Changed', dotColor: 'var(--warning-color)' }, + building: { label: 'Building', dotColor: 'var(--primary-color)' }, + queued: { label: 'Queued', dotColor: 'var(--on-background-low-color)' }, +}; + +export interface StatusPillsProps { + statuses: Set; + onToggle: (status: ComponentStatus) => void; + counts: Record; + visibleStatuses?: ComponentStatus[]; +} + +export function StatusPills({ + statuses, + onToggle, + counts, + visibleStatuses = ['building', 'changed', 'queued'], +}: StatusPillsProps) { + return ( + <> + {visibleStatuses.map((s) => { + const active = statuses.has(s); + const meta = STATUS_META[s]; + const isBuilding = s === 'building' && active; + + return ( + + ); + })} + + ); +} From 249d6f20b0afd5415df9a19316769e76abb32eca Mon Sep 17 00:00:00 2001 From: Luv Kapur Date: Fri, 1 May 2026 17:09:03 -0400 Subject: [PATCH 06/13] feat(workspace-overview): restructure WorkspaceFilterPanel with status pills and density toggle Co-Authored-By: Claude Opus 4.6 --- .../workspace-filter-panel.tsx | 38 +++++++++++++++---- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/scopes/workspace/workspace/ui/workspace/workspace-overview/workspace-filter-panel.tsx b/scopes/workspace/workspace/ui/workspace/workspace-overview/workspace-filter-panel.tsx index b9ab3d1ca7aa..c9896854f175 100644 --- a/scopes/workspace/workspace/ui/workspace/workspace-overview/workspace-filter-panel.tsx +++ b/scopes/workspace/workspace/ui/workspace/workspace-overview/workspace-filter-panel.tsx @@ -1,7 +1,10 @@ import React, { useMemo } from 'react'; import { ToggleButton } from '@teambit/design.inputs.toggle-button'; import { BaseFilter } from '@teambit/component.filters.base-filter'; -import type { WorkspaceItem, AggregationType } from './workspace-overview.types'; +import { StatusPills } from './status-pills'; +import { DensityToggle } from './density-toggle'; +import type { WorkspaceItem, AggregationType, Density, ComponentStatus } from './workspace-overview.types'; +import { getComponentStatus } from './filter-utils'; import styles from './workspace-overview.module.scss'; export interface WorkspaceFilterPanelProps { @@ -13,6 +16,10 @@ export interface WorkspaceFilterPanelProps { onNamespacesChange: (namespaces: string[]) => void; activeScopes: string[]; onScopesChange: (scopes: string[]) => void; + statuses: Set; + onToggleStatus: (status: ComponentStatus) => void; + density: Density; + onDensityChange: (density: Density) => void; } const LABELS: Record = { @@ -30,6 +37,10 @@ export function WorkspaceFilterPanel({ onNamespacesChange, activeScopes, onScopesChange, + statuses, + onToggleStatus, + density, + onDensityChange, }: WorkspaceFilterPanelProps) { const namespaceOptions = useMemo( () => @@ -47,15 +58,24 @@ export function WorkspaceFilterPanel({ [items] ); + const counts = useMemo(() => { + const c: Record = { built: 0, changed: 0, building: 0, queued: 0 }; + for (const item of items) { + const s = getComponentStatus(item); + c[s]++; + } + return c; + }, [items]); + const activeNsOptions = activeNamespaces.map((v) => ({ value: v })); const activeScopeOptions = activeScopes.map((v) => ({ value: v })); - const applyNs = (opts) => { + const applyNs = (opts: { value?: string }[]) => { const list = opts.map((o) => o.value).filter((v): v is string => typeof v === 'string'); onNamespacesChange(list); }; - const applyScopes = (opts) => { + const applyScopes = (opts: { value?: string }[]) => { const list = opts.map((o) => o.value).filter((v): v is string => typeof v === 'string'); onScopesChange(list); }; @@ -71,8 +91,8 @@ export function WorkspaceFilterPanel({ }; return ( -
-
+
+
- + +
-
+
+ applyAgg(idx)} options={availableAggregations.map((agg) => ({ From e0f9c28660b147afac5373ebd40cea110e35fee3 Mon Sep 17 00:00:00 2001 From: Luv Kapur Date: Fri, 1 May 2026 17:09:07 -0400 Subject: [PATCH 07/13] feat(workspace-overview): update SCSS for new layout, command bar, density-driven grid, card overrides Co-Authored-By: Claude Opus 4.6 --- .../workspace-overview.module.scss | 273 +++++++++--------- 1 file changed, 133 insertions(+), 140 deletions(-) diff --git a/scopes/workspace/workspace/ui/workspace/workspace-overview/workspace-overview.module.scss b/scopes/workspace/workspace/ui/workspace/workspace-overview/workspace-overview.module.scss index bc3518a6c5ea..8f88b7507ebf 100644 --- a/scopes/workspace/workspace/ui/workspace/workspace-overview/workspace-overview.module.scss +++ b/scopes/workspace/workspace/ui/workspace/workspace-overview/workspace-overview.module.scss @@ -1,211 +1,204 @@ @import '@teambit/ui-foundation.ui.constants.z-indexes/z-indexes.module.scss'; .container { - padding: 24px 5% 150px 5%; + max-width: 1320px; + margin: 0 auto; + padding: 20px 40px 80px; overflow-y: auto; height: 100%; box-sizing: border-box; z-index: 1; } -.rightPreviewPlugins { - display: flex; - align-items: flex-end; - justify-content: flex-end; - width: 100%; - height: 100%; -} - -.envIcon { - height: 14px; -} - -.badge { - background-color: var(--surface-color); - padding: 4px; - height: 15px; - margin-right: 4px; - z-index: $nav-z-index; - border-radius: 8px; - margin-bottom: 4px; - box-shadow: 0px 2px 18px rgb(0 0 0 / 10%); -} - -.cardGrid { - display: grid; - gap: 32px 24px; - grid-template-columns: repeat(auto-fill, minmax(270px, 1fr)); - max-width: 1280px; - grid-column-gap: 28px; - grid-row-gap: 32px; -} +/* ---- Command bar ---- */ -.filterPanel { +.commandBar { display: flex; align-items: center; justify-content: space-between; - margin-bottom: 24px; + gap: 12px; + flex-wrap: wrap; + padding-bottom: 18px; position: relative; z-index: $modal-z-index - 1; } -.aggButtons { - display: inline-flex; - background: var(--surface-1-color); - border: 1px solid var(--border-medium-color); - border-radius: 8px; - padding: 2px; - height: 34px; -} - -.aggButton, -.aggActive { - padding: 4px 12px; - font-size: 14px; - border-radius: 6px; - background: transparent; - cursor: pointer; - border: none; +.leftCluster { display: flex; align-items: center; -} - -.aggButton:hover { - background: var(--surface-hover-color); -} - -.aggActive { - background: var(--brand-primary-color); - color: white; -} - -.dropdownList { - max-height: 260px; - overflow-y: auto; - padding: 0 12px; - display: flex; - flex-direction: column; gap: 8px; + flex-wrap: wrap; } -.dropdownItem { +.rightCluster { display: flex; align-items: center; gap: 8px; - font-size: 14px; - cursor: pointer; -} - -.dropdownItem input { - width: 16px; - height: 16px; -} - -.count { - opacity: 0.55; + margin-left: auto; } -.agg { - margin-bottom: 28px; +.verticalDivider { + width: 1px; + height: 18px; + background: var(--border-medium-color); + margin: 0 4px; } -.aggregationTitle { - margin-top: 0; - margin-bottom: 15px; +.aggToggle { + height: 32px !important; + font-size: 12.5px; } -:global(.componentGrid) { - grid-row-gap: 24px !important; - grid-column-gap: 16px !important; -} +/* ---- Base filter overrides ---- */ -.filterPanel :global(.baseFilter) { +.commandBar :global(.baseFilter) { max-width: 220px; height: 32px; } -.filterPanel :global(.control) { +.commandBar :global(.control) { border-radius: 8px !important; height: 32px; padding: 0 10px !important; } -.filterPanel :global(.menu) { +.commandBar :global(.menu) { z-index: $modal-z-index - 1 !important; - border-radius: 8px !important; + border-radius: 14px !important; padding: 8px 0 !important; + width: 260px; + box-shadow: 0 24px 60px -12px rgba(20, 0, 104, 0.2); } -.filterDropdown :global(.baseFilter) { - height: 32px; - max-width: 240px; - font-size: 14px; -} +/* ---- Section ---- */ -.filterDropdown :global(.baseFilter .control) { - padding: 8px 12px; - border-radius: 8px; +.section { + margin-bottom: 36px; } -.filterDropdown :global(.menu) { - margin-top: 10px; - padding: 12px 0; - min-width: 260px; - border-radius: 12px; +/* ---- Card grid ---- */ - box-shadow: var(--bit-shadow-hover-low, 0 2px 8px rgba(0, 0, 0, 0.1)); - border: 1px solid var(--border-medium-color, #ededed); +.cardGrid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(var(--card-min, 280px), 1fr)); + gap: 14px; } -.filterDropdown :global(.checkboxContainer) { - padding-left: 14px; +/* ---- Card overrides (applied via parent wrapper) ---- */ + +.cardWrapper { + position: relative; } -.filterDropdown :global(.checkbox) { - margin-right: 12px; - accent-color: var(--bit-accent-color, #6c5ce7); +.cardWrapper :global(.componentCard) { + border-radius: 10px; + transition: + box-shadow 0.15s ease, + border-color 0.15s ease; } -.filterDropdown :global(.buttonsSection) { - padding: 12px 16px; - border-top: 1px solid var(--border-medium-color, #ededed); +.cardWrapper :global(.componentCard):hover { + box-shadow: 0 4px 14px rgba(20, 0, 104, 0.06); } -.filterDropdown :global(.placeholder > span:not(:first-child)) { - margin-left: 8px; +.cardWrapper :global(.previewContainer) { + padding-bottom: 0; + height: var(--preview-h, 180px); } -.filterPanel { +/* ---- Preview plugins ---- */ + +.rightPreviewPlugins { display: flex; - align-items: center; - justify-content: space-between; - margin-bottom: 24px; - gap: 16px; + align-items: flex-end; + justify-content: flex-end; + width: 100%; + height: 100%; } -.leftFilters { - display: flex; - gap: 12px; - align-items: center; +.envIcon { + height: 14px; } -.rightAggToggle { +.badge { + background-color: var(--surface-color); + padding: 4px; + height: 15px; + margin-right: 4px; + z-index: $nav-z-index; + border-radius: 8px; + margin-bottom: 4px; + box-shadow: 0px 2px 18px rgb(0 0 0 / 10%); +} + +/* ---- Card footer ---- */ + +.cardFooter { + padding: 8px 12px; display: flex; align-items: center; - margin-left: auto; - height: 32px; - font-size: 14px; + gap: 8px; } -.toggleBtn { - height: 32px !important; - font-size: 14px; +.cardName { + flex: 1; + min-width: 0; + font-size: 12.5px; + font-weight: 500; + font-family: 'JetBrains Mono', ui-monospace, Menlo, monospace; + color: var(--on-background-color); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + letter-spacing: -0.01em; } -.cardGrid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(270px, 1fr)); - grid-row-gap: 32px; - grid-column-gap: 24px; - max-width: 1280px; +.cardNameQueued { + composes: cardName; + color: var(--on-background-medium-color); +} + +.hashChip { + font-size: 10.5px; + padding: 1px 6px; + border-radius: 4px; + background: var(--surface01-color); + font-family: 'JetBrains Mono', ui-monospace, Menlo, monospace; + color: var(--on-background-low-color); + flex-shrink: 0; +} + +.buildingLabel { + font-size: 10px; + font-weight: 600; + letter-spacing: 0.06em; + text-transform: uppercase; + flex-shrink: 0; +} + +/* ---- Status corner ---- */ + +.statusCorner { + position: absolute; + top: 10px; + right: 10px; + z-index: 2; +} + +/* ---- Building card border ---- */ + +.cardBuilding :global(.componentCard) { + transition: + box-shadow 0.15s ease, + border-color 0.15s ease; +} + +.cardBuilding :global(.componentCard):hover { + box-shadow: none; +} + +/* ---- Queued preview bg ---- */ + +.previewQueued { + background: var(--surface01-color) !important; } From 27d0e4063a6cdd0202b20362944753f88db25535 Mon Sep 17 00:00:00 2001 From: Luv Kapur Date: Fri, 1 May 2026 17:11:53 -0400 Subject: [PATCH 08/13] feat(workspace-overview): wire up density, status filters, namespace headers, card overlays, and empty state Co-Authored-By: Claude Opus 4.6 --- .../workspace-overview/workspace-overview.tsx | 72 ++++++++++++++++--- 1 file changed, 63 insertions(+), 9 deletions(-) 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 8089b5c74e4d..8ead0da63222 100644 --- a/scopes/workspace/workspace/ui/workspace/workspace-overview/workspace-overview.tsx +++ b/scopes/workspace/workspace/ui/workspace/workspace-overview/workspace-overview.tsx @@ -1,4 +1,4 @@ -import React, { useContext, useMemo } from 'react'; +import React, { useCallback, useContext, useMemo, useState } from 'react'; import { ComponentGrid } from '@teambit/explorer.ui.gallery.component-grid'; import { EmptyWorkspace } from '@teambit/workspace.ui.empty-workspace'; import { PreviewPlaceholder } from '@teambit/preview.ui.preview-placeholder'; @@ -11,12 +11,16 @@ import { useCloudScopes } from '@teambit/cloud.hooks.use-cloud-scopes'; import { WorkspaceComponentCard } from '@teambit/workspace.ui.workspace-component-card'; import type { ComponentCardPluginType, PluginProps } from '@teambit/explorer.ui.component-card'; import { useWorkspaceMode } from '@teambit/workspace.ui.use-workspace-mode'; -import { H3 } from '@teambit/design.ui.heading'; import { WorkspaceContext } from '../workspace-context'; import { LinkPlugin } from './link-plugin'; import { useWorkspaceAggregation } from './use-workspace-aggregation'; import { useQueryParamWithDefault, useListParamWithDefault } from './use-query-param-with-default'; -import type { AggregationType } from './workspace-overview.types'; +import { getComponentStatus, ALL_STATUSES } from './filter-utils'; +import { getAccent } from './namespace-hues'; +import { NamespaceHeader } from './namespace-header'; +import { EmptyFilters } from './empty-filters'; +import { BuildingPreview, QueuedPreview } from './card-overlays'; +import type { AggregationType, Density, ComponentStatus } from './workspace-overview.types'; import { WorkspaceFilterPanel } from './workspace-filter-panel'; import styles from './workspace-overview.module.scss'; @@ -55,10 +59,32 @@ export function WorkspaceOverview() { const [aggregation, setAggregation] = useQueryParamWithDefault('aggregation', 'namespaces'); const [activeNamespaces, setActiveNamespaces] = useListParamWithDefault('ns'); const [activeScopes, setActiveScopes] = useListParamWithDefault('scopes'); + const [density, setDensity] = useQueryParamWithDefault('density', 'comfy'); + + const [statuses, setStatuses] = useState>(() => new Set(ALL_STATUSES)); + + const toggleStatus = useCallback((s: ComponentStatus) => { + setStatuses((prev) => { + const next = new Set(prev); + if (next.has(s)) { + next.delete(s); + if (next.size === 0) return prev; + } else { + next.add(s); + } + return next; + }); + }, []); + + const clearAllFilters = useCallback(() => { + setActiveNamespaces([]); + setActiveScopes([]); + setStatuses(new Set(ALL_STATUSES)); + }, [setActiveNamespaces, setActiveScopes]); const filters = useMemo( - () => ({ namespaces: activeNamespaces, scopes: activeScopes }), - [activeNamespaces, activeScopes] + () => ({ namespaces: activeNamespaces, scopes: activeScopes, statuses }), + [activeNamespaces, activeScopes, statuses] ); const { groups, groupType, availableAggregations, filteredCount } = useWorkspaceAggregation( @@ -67,8 +93,16 @@ export function WorkspaceOverview() { filters ); + const cardMin = density === 'compact' ? 220 : 280; + const previewH = density === 'compact' ? 130 : 180; + const plugins = useCardPlugins({ compModelsById, showPreview: isMinimal }); + const gridStyle = { + '--card-min': `${cardMin}px`, + '--preview-h': `${previewH}px`, + } as React.CSSProperties; + return (
- {filteredCount === 0 && } + {filteredCount === 0 && } {groups.map((group) => ( -
- {groupType !== 'none' &&

{group.displayName}

} +
+ {groupType !== 'none' && } - + {group.items.map((item) => ( ; + if (status === 'building') return ; + return ( Date: Mon, 4 May 2026 11:28:39 -0400 Subject: [PATCH 09/13] re design --- scopes/docs/docs/overview/overview.tsx | 1 + .../density-toggle.module.scss | 30 --- .../workspace-overview/density-toggle.tsx | 47 ----- .../empty-filters.module.scss | 35 ---- .../workspace-overview/empty-filters.tsx | 18 -- .../hope-component-card.module.scss | 162 +++++++++++++++ .../hope-component-card.tsx | 154 +++++++++++++++ .../namespace-header.module.scss | 37 +++- .../workspace-overview/namespace-header.tsx | 37 +++- .../status-pills.module.scss | 57 ------ .../workspace-overview/status-pills.tsx | 55 ------ .../use-workspace-aggregation.ts | 16 +- .../workspace-filter-panel.tsx | 25 +-- .../workspace-overview.module.scss | 155 +++------------ .../workspace-overview/workspace-overview.tsx | 186 ++++-------------- .../workspace-overview.types.ts | 4 +- .../ui/workspace/workspace.module.scss | 50 +++-- .../workspace/ui/workspace/workspace.tsx | 9 +- 18 files changed, 501 insertions(+), 577 deletions(-) delete mode 100644 scopes/workspace/workspace/ui/workspace/workspace-overview/density-toggle.module.scss delete mode 100644 scopes/workspace/workspace/ui/workspace/workspace-overview/density-toggle.tsx delete mode 100644 scopes/workspace/workspace/ui/workspace/workspace-overview/empty-filters.module.scss delete mode 100644 scopes/workspace/workspace/ui/workspace/workspace-overview/empty-filters.tsx create mode 100644 scopes/workspace/workspace/ui/workspace/workspace-overview/hope-component-card.module.scss create mode 100644 scopes/workspace/workspace/ui/workspace/workspace-overview/hope-component-card.tsx delete mode 100644 scopes/workspace/workspace/ui/workspace/workspace-overview/status-pills.module.scss delete mode 100644 scopes/workspace/workspace/ui/workspace/workspace-overview/status-pills.tsx diff --git a/scopes/docs/docs/overview/overview.tsx b/scopes/docs/docs/overview/overview.tsx index 85325173a1d7..4fffcbe828b4 100644 --- a/scopes/docs/docs/overview/overview.tsx +++ b/scopes/docs/docs/overview/overview.tsx @@ -159,6 +159,7 @@ export function Overview({ viewport={null} fullContentHeight disableScroll={true} + propagateError={isMinimal} sandbox={sandboxValue} {...rest} component={component} diff --git a/scopes/workspace/workspace/ui/workspace/workspace-overview/density-toggle.module.scss b/scopes/workspace/workspace/ui/workspace/workspace-overview/density-toggle.module.scss deleted file mode 100644 index 776d0b4a9021..000000000000 --- a/scopes/workspace/workspace/ui/workspace/workspace-overview/density-toggle.module.scss +++ /dev/null @@ -1,30 +0,0 @@ -.track { - display: inline-flex; - background: var(--surface01-color); - border-radius: 8px; - padding: 2px; - height: 32px; - border: 1px solid var(--border-medium-color); -} - -.option { - width: 30px; - height: 26px; - border-radius: 6px; - background: transparent; - color: var(--on-background-low-color); - border: none; - cursor: pointer; - display: inline-flex; - align-items: center; - justify-content: center; - box-shadow: none; - transition: all 0.15s ease; -} - -.optionActive { - composes: option; - background: var(--surface-color); - color: var(--on-background-color); - box-shadow: 0 1px 2px rgba(20, 0, 104, 0.06); -} diff --git a/scopes/workspace/workspace/ui/workspace/workspace-overview/density-toggle.tsx b/scopes/workspace/workspace/ui/workspace/workspace-overview/density-toggle.tsx deleted file mode 100644 index 6be5b4acceff..000000000000 --- a/scopes/workspace/workspace/ui/workspace/workspace-overview/density-toggle.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import React from 'react'; -import type { Density } from './workspace-overview.types'; -import styles from './density-toggle.module.scss'; - -export interface DensityToggleProps { - value: Density; - onChange: (density: Density) => void; -} - -const CompactIcon = () => ( - - - - - - -); - -const ComfyIcon = () => ( - - - - -); - -const OPTIONS: { value: Density; icon: React.ReactNode }[] = [ - { value: 'compact', icon: }, - { value: 'comfy', icon: }, -]; - -export function DensityToggle({ value, onChange }: DensityToggleProps) { - return ( -
- {OPTIONS.map((o) => ( - - ))} -
- ); -} diff --git a/scopes/workspace/workspace/ui/workspace/workspace-overview/empty-filters.module.scss b/scopes/workspace/workspace/ui/workspace/workspace-overview/empty-filters.module.scss deleted file mode 100644 index 13ce4fc18f84..000000000000 --- a/scopes/workspace/workspace/ui/workspace/workspace-overview/empty-filters.module.scss +++ /dev/null @@ -1,35 +0,0 @@ -.container { - text-align: center; - padding: 80px 20px; - color: var(--on-background-medium-color); -} - -.headline { - font-family: 'Instrument Serif', Georgia, serif; - font-weight: 400; - font-size: 28px; - color: var(--on-background-color); - margin-bottom: 10px; - letter-spacing: -0.01em; -} - -.body { - font-size: 13px; - margin-bottom: 18px; -} - -.clearButton { - height: 34px; - padding: 0 18px; - border-radius: 999px; - background: var(--surface-primary-color); - border: none; - font-size: 13px; - font-weight: 500; - cursor: pointer; - color: white; -} - -.clearButton:hover { - background: var(--surface-primary-hover-color); -} diff --git a/scopes/workspace/workspace/ui/workspace/workspace-overview/empty-filters.tsx b/scopes/workspace/workspace/ui/workspace/workspace-overview/empty-filters.tsx deleted file mode 100644 index 4a9b5e404eab..000000000000 --- a/scopes/workspace/workspace/ui/workspace/workspace-overview/empty-filters.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import React from 'react'; -import styles from './empty-filters.module.scss'; - -export interface EmptyFiltersProps { - onClear: () => void; -} - -export function EmptyFilters({ onClear }: EmptyFiltersProps) { - return ( -
-
Nothing matches.
-
Try clearing namespace, scope, or status filters.
- -
- ); -} diff --git a/scopes/workspace/workspace/ui/workspace/workspace-overview/hope-component-card.module.scss b/scopes/workspace/workspace/ui/workspace/workspace-overview/hope-component-card.module.scss new file mode 100644 index 000000000000..cbfa73bd22ac --- /dev/null +++ b/scopes/workspace/workspace/ui/workspace/workspace-overview/hope-component-card.module.scss @@ -0,0 +1,162 @@ +.card { + position: relative; + border-radius: 10px; + overflow: hidden; + background: var(--surface-color); + border: 1px solid var(--border-medium-color); + cursor: pointer; + transition: + box-shadow 0.15s ease, + border-color 0.15s ease; + + &:hover { + box-shadow: 0 4px 14px rgba(20, 0, 104, 0.06); + } +} + +.cardBuilding { + composes: card; + + &:hover { + box-shadow: none; + } +} + +.linkWrapper { + text-decoration: none; + color: inherit; + display: block; +} + +/* ---- Preview ---- */ + +.preview { + height: 180px; + position: relative; + overflow: hidden; + background: white; + border-bottom: 1px solid var(--border-medium-color); +} + +.previewQueued { + composes: preview; + background: var(--surface01-color); +} + +.previewInner { + position: absolute; + inset: 0; + overflow: hidden; +} + +/* ---- Env badge ---- */ + +.envBadge { + position: absolute; + bottom: 8px; + right: 8px; + background: var(--surface-color); + padding: 4px; + border-radius: 8px; + box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1); + display: flex; + align-items: center; + justify-content: center; + width: 24px; + height: 24px; + z-index: 3; +} + +.envIcon { + height: 14px; + display: block; +} + +/* ---- Status corner ---- */ + +.statusCorner { + position: absolute; + top: 10px; + right: 10px; + z-index: 2; +} + +/* ---- Footer ---- */ + +.footer { + padding: 8px 12px; + display: flex; + align-items: center; + gap: 8px; +} + +.name { + flex: 1; + min-width: 0; + font-size: 12.5px; + font-weight: 500; + font-family: 'JetBrains Mono', ui-monospace, Menlo, monospace; + color: var(--on-background-color); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + letter-spacing: -0.01em; +} + +.nameQueued { + composes: name; + color: var(--on-background-medium-color); +} + +.hash { + font-size: 10.5px; + padding: 1px 6px; + border-radius: 4px; + background: var(--surface01-color); + font-family: 'JetBrains Mono', ui-monospace, Menlo, monospace; + color: var(--on-background-low-color); + flex-shrink: 0; +} + +.scopeBadge { + width: 20px; + height: 20px; + border-radius: 5px; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + overflow: hidden; +} + +.scopeBadgeIcon { + width: 12px; + height: 12px; + object-fit: contain; + filter: brightness(0) invert(1); +} + +.scopeBadgeInitial { + font-family: 'JetBrains Mono', ui-monospace, Menlo, monospace; + font-size: 10px; + font-weight: 700; + color: white; + line-height: 1; +} + +.buildingLabel { + font-size: 10px; + font-weight: 600; + letter-spacing: 0.06em; + text-transform: uppercase; + flex-shrink: 0; +} + +/* ---- Load preview button ---- */ + +.loadPreview { + position: absolute; + right: 4px !important; + left: unset !important; + z-index: 4; +} diff --git a/scopes/workspace/workspace/ui/workspace/workspace-overview/hope-component-card.tsx b/scopes/workspace/workspace/ui/workspace/workspace-overview/hope-component-card.tsx new file mode 100644 index 000000000000..91667139cf1d --- /dev/null +++ b/scopes/workspace/workspace/ui/workspace/workspace-overview/hope-component-card.tsx @@ -0,0 +1,154 @@ +import React, { useEffect, useRef, useState } from 'react'; +import { Link } from '@teambit/base-react.navigation.link'; +import { PreviewPlaceholder } from '@teambit/preview.ui.preview-placeholder'; +import { Tooltip } from '@teambit/design.ui.tooltip'; +import { LoadPreview } from '@teambit/workspace.ui.load-preview'; +import { ComponentID } from '@teambit/component-id'; +import type { ComponentModel } from '@teambit/component'; +import type { ComponentDescriptor } from '@teambit/component-descriptor'; +import type { ScopeID } from '@teambit/scopes.scope-id'; +import { getComponentStatus } from './filter-utils'; +import { getAccent } from './namespace-hues'; +import { ChangedPill, BuildSpinner, BuildingPreview, QueuedPreview } from './card-overlays'; +import styles from './hope-component-card.module.scss'; + +export type HopeComponentCardProps = { + component: ComponentModel; + componentDescriptor: ComponentDescriptor; + scope?: { id: ScopeID; icon?: string; backgroundIconColor?: string }; + showPreview?: boolean; +}; + +export function HopeComponentCard({ + component, + componentDescriptor, + scope, + showPreview: showPreviewProp, +}: HopeComponentCardProps) { + const [shouldShowPreview, setShouldShowPreview] = useState(Boolean(showPreviewProp)); + const prevServerUrlRef = useRef(component.server?.url); + + useEffect(() => { + if (prevServerUrlRef.current !== component.server?.url && shouldShowPreview) { + setShouldShowPreview(false); + setTimeout(() => setShouldShowPreview(true), 50); + } + prevServerUrlRef.current = component.server?.url; + }, [component.server?.url]); + + useEffect(() => { + setShouldShowPreview(Boolean(showPreviewProp)); + }, [showPreviewProp]); + + const item = { component } as any; + const status = getComponentStatus(item); + const ns = component.id.namespace || '/'; + const accent = getAccent(ns); + + const isBuilding = status === 'building'; + const isQueued = status === 'queued'; + const isChanged = status === 'changed'; + + const href = `${component.id.fullName}?scope=${component.id.scope}`; + + const loadPreviewVisible = + component.compositions.length > 0 && component.buildStatus !== 'pending' && !shouldShowPreview; + + const showPreviewClick = (e: React.MouseEvent) => { + e.stopPropagation(); + e.preventDefault(); + setShouldShowPreview(true); + }; + + const envAspect = componentDescriptor.get('teambit.envs/envs'); + const env = envAspect?.data || envAspect; + const envComponentId = env?.id ? ComponentID.fromString(env.id) : undefined; + + const cardClass = isBuilding ? styles.cardBuilding : styles.card; + const buildingBorderStyle = isBuilding ? { borderColor: accent, boxShadow: `0 0 0 3px ${accent}1A` } : undefined; + + const nameLabel = component.id.namespace ? `${component.id.namespace}/${component.id.name}` : component.id.name; + + const shortHash = component.id.version?.slice(0, 7); + + const scopeInitial = component.id.scope?.split('.').pop()?.charAt(0).toUpperCase(); + + return ( +
+ {loadPreviewVisible && } + + +
+
+ +
+ + {!isQueued && env?.icon && ( +
+ + + +
+ )} + + {(isChanged || isBuilding) && ( +
+ {isChanged && } + {isBuilding && } +
+ )} +
+ +
+ +
+ {scope?.icon ? ( + + ) : ( + {scopeInitial} + )} +
+
+ {nameLabel} + {!isBuilding && !isQueued && shortHash && {shortHash}} + {isBuilding && ( + + BUILDING + + )} +
+ +
+ ); +} + +function CardPreview({ + component, + componentDescriptor, + status, + accent, + shouldShowPreview, +}: { + component: ComponentModel; + componentDescriptor: ComponentDescriptor; + status: string; + accent: string; + shouldShowPreview: boolean; +}) { + if (status === 'queued') return ; + if (status === 'building') return ; + + return ( + + ); +} diff --git a/scopes/workspace/workspace/ui/workspace/workspace-overview/namespace-header.module.scss b/scopes/workspace/workspace/ui/workspace/workspace-overview/namespace-header.module.scss index 81b424d47fd5..257ffd4c3331 100644 --- a/scopes/workspace/workspace/ui/workspace/workspace-overview/namespace-header.module.scss +++ b/scopes/workspace/workspace/ui/workspace/workspace-overview/namespace-header.module.scss @@ -2,21 +2,20 @@ display: flex; align-items: center; gap: 10px; - margin-bottom: 12px; } .dot { - width: 8px; - height: 8px; - border-radius: 2px; + width: 10px; + height: 10px; + border-radius: 3px; flex-shrink: 0; } .name { font-family: 'JetBrains Mono', ui-monospace, Menlo, monospace; - font-size: 11px; + font-size: 13px; font-weight: 600; - letter-spacing: 0.12em; + letter-spacing: 0.1em; text-transform: uppercase; color: var(--on-background-color); flex-shrink: 0; @@ -48,6 +47,32 @@ animation: pulse 1.4s ease-in-out infinite; } +.scopeIconBadge { + width: 22px; + height: 22px; + border-radius: 6px; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + overflow: hidden; +} + +.scopeIconImg { + width: 14px; + height: 14px; + object-fit: contain; + filter: brightness(0) invert(1); +} + +.scopeInitial { + font-family: 'JetBrains Mono', ui-monospace, Menlo, monospace; + font-size: 11px; + font-weight: 700; + color: white; + line-height: 1; +} + .divider { flex: 1; height: 1px; diff --git a/scopes/workspace/workspace/ui/workspace/workspace-overview/namespace-header.tsx b/scopes/workspace/workspace/ui/workspace/workspace-overview/namespace-header.tsx index 9f3d37b902e9..f2d381aece92 100644 --- a/scopes/workspace/workspace/ui/workspace/workspace-overview/namespace-header.tsx +++ b/scopes/workspace/workspace/ui/workspace/workspace-overview/namespace-header.tsx @@ -7,9 +7,11 @@ import styles from './namespace-header.module.scss'; export interface NamespaceHeaderProps { namespace: string; items: WorkspaceItem[]; + scopeIcon?: string; + scopeIconColor?: string; } -export function NamespaceHeader({ namespace, items }: NamespaceHeaderProps) { +export function NamespaceHeader({ namespace, items, scopeIcon, scopeIconColor }: NamespaceHeaderProps) { const accent = getAccent(namespace); const tint = getTint(namespace); @@ -23,7 +25,7 @@ export function NamespaceHeader({ namespace, items }: NamespaceHeaderProps) { return (
- + {namespace} {readyCount}/{items.length} @@ -38,3 +40,34 @@ export function NamespaceHeader({ namespace, items }: NamespaceHeaderProps) {
); } + +function HeaderIcon({ + scopeIcon, + scopeIconColor, + namespace, + accent, +}: { + scopeIcon?: string; + scopeIconColor?: string; + namespace: string; + accent: string; +}) { + if (scopeIcon) { + return ( +
+ +
+ ); + } + + if (scopeIconColor) { + const initial = namespace.split(/[./]/).pop()?.charAt(0).toUpperCase() || '?'; + return ( +
+ {initial} +
+ ); + } + + return ; +} diff --git a/scopes/workspace/workspace/ui/workspace/workspace-overview/status-pills.module.scss b/scopes/workspace/workspace/ui/workspace/workspace-overview/status-pills.module.scss deleted file mode 100644 index 1413dde1c837..000000000000 --- a/scopes/workspace/workspace/ui/workspace/workspace-overview/status-pills.module.scss +++ /dev/null @@ -1,57 +0,0 @@ -.pill { - display: inline-flex; - align-items: center; - gap: 7px; - height: 30px; - padding: 0 12px; - border-radius: 999px; - border: none; - font-size: 12px; - font-weight: 500; - cursor: pointer; - transition: all 0.15s ease; - background: transparent; - color: var(--on-background-low-color); -} - -.active { - background: var(--surface01-color); - color: var(--on-background-color); -} - -.dot { - width: 7px; - height: 7px; - border-radius: 50%; - background: var(--border-high-color); - flex-shrink: 0; -} - -.dotActive { - composes: dot; -} - -.dotBuilding { - composes: dotActive; - animation: pulse 1.4s ease-in-out infinite; -} - -.pulsingRing { - box-shadow: 0 0 0 3px rgba(93, 72, 255, 0.2); -} - -.count { - font-size: 10px; - opacity: 0.6; - font-family: 'JetBrains Mono', ui-monospace, Menlo, monospace; -} - -@keyframes pulse { - 0%, - 100% { - opacity: 1; - } - 50% { - opacity: 0.55; - } -} diff --git a/scopes/workspace/workspace/ui/workspace/workspace-overview/status-pills.tsx b/scopes/workspace/workspace/ui/workspace/workspace-overview/status-pills.tsx deleted file mode 100644 index a7b3f891969c..000000000000 --- a/scopes/workspace/workspace/ui/workspace/workspace-overview/status-pills.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import React from 'react'; -import classNames from 'classnames'; -import type { ComponentStatus } from './workspace-overview.types'; -import styles from './status-pills.module.scss'; - -const STATUS_META: Record = { - built: { label: 'Built', dotColor: 'var(--positive-color)' }, - changed: { label: 'Changed', dotColor: 'var(--warning-color)' }, - building: { label: 'Building', dotColor: 'var(--primary-color)' }, - queued: { label: 'Queued', dotColor: 'var(--on-background-low-color)' }, -}; - -export interface StatusPillsProps { - statuses: Set; - onToggle: (status: ComponentStatus) => void; - counts: Record; - visibleStatuses?: ComponentStatus[]; -} - -export function StatusPills({ - statuses, - onToggle, - counts, - visibleStatuses = ['building', 'changed', 'queued'], -}: StatusPillsProps) { - return ( - <> - {visibleStatuses.map((s) => { - const active = statuses.has(s); - const meta = STATUS_META[s]; - const isBuilding = s === 'building' && active; - - return ( - - ); - })} - - ); -} diff --git a/scopes/workspace/workspace/ui/workspace/workspace-overview/use-workspace-aggregation.ts b/scopes/workspace/workspace/ui/workspace/workspace-overview/use-workspace-aggregation.ts index b9398fd6e44d..5cd9eab9e747 100644 --- a/scopes/workspace/workspace/ui/workspace/workspace-overview/use-workspace-aggregation.ts +++ b/scopes/workspace/workspace/ui/workspace/workspace-overview/use-workspace-aggregation.ts @@ -58,11 +58,17 @@ export function useWorkspaceAggregation( const sortedScopes = [...map.keys()].sort(); - const groups: AggregationGroup[] = sortedScopes.map((sc) => ({ - name: sc, - displayName: sc, - items: sortItemsByNamespace(map.get(sc)!), - })); + const groups: AggregationGroup[] = sortedScopes.map((sc) => { + const groupItems = map.get(sc)!; + const sampleScope = groupItems.find((i) => i.scope?.icon)?.scope; + return { + name: sc, + displayName: sc, + items: sortItemsByNamespace(groupItems), + scopeIcon: sampleScope?.icon, + scopeIconColor: sampleScope?.backgroundIconColor, + }; + }); return { groups, diff --git a/scopes/workspace/workspace/ui/workspace/workspace-overview/workspace-filter-panel.tsx b/scopes/workspace/workspace/ui/workspace/workspace-overview/workspace-filter-panel.tsx index c9896854f175..1a551c719956 100644 --- a/scopes/workspace/workspace/ui/workspace/workspace-overview/workspace-filter-panel.tsx +++ b/scopes/workspace/workspace/ui/workspace/workspace-overview/workspace-filter-panel.tsx @@ -1,10 +1,7 @@ import React, { useMemo } from 'react'; import { ToggleButton } from '@teambit/design.inputs.toggle-button'; import { BaseFilter } from '@teambit/component.filters.base-filter'; -import { StatusPills } from './status-pills'; -import { DensityToggle } from './density-toggle'; -import type { WorkspaceItem, AggregationType, Density, ComponentStatus } from './workspace-overview.types'; -import { getComponentStatus } from './filter-utils'; +import type { WorkspaceItem, AggregationType } from './workspace-overview.types'; import styles from './workspace-overview.module.scss'; export interface WorkspaceFilterPanelProps { @@ -16,10 +13,6 @@ export interface WorkspaceFilterPanelProps { onNamespacesChange: (namespaces: string[]) => void; activeScopes: string[]; onScopesChange: (scopes: string[]) => void; - statuses: Set; - onToggleStatus: (status: ComponentStatus) => void; - density: Density; - onDensityChange: (density: Density) => void; } const LABELS: Record = { @@ -37,10 +30,6 @@ export function WorkspaceFilterPanel({ onNamespacesChange, activeScopes, onScopesChange, - statuses, - onToggleStatus, - density, - onDensityChange, }: WorkspaceFilterPanelProps) { const namespaceOptions = useMemo( () => @@ -58,15 +47,6 @@ export function WorkspaceFilterPanel({ [items] ); - const counts = useMemo(() => { - const c: Record = { built: 0, changed: 0, building: 0, queued: 0 }; - for (const item of items) { - const s = getComponentStatus(item); - c[s]++; - } - return c; - }, [items]); - const activeNsOptions = activeNamespaces.map((v) => ({ value: v })); const activeScopeOptions = activeScopes.map((v) => ({ value: v })); @@ -109,12 +89,9 @@ export function WorkspaceFilterPanel({ onChange={applyScopes} isSearchable /> - -
- ; const { isMinimal } = useWorkspaceMode(); - const compModelsById = useMemo(() => new Map(components.map((c) => [c.id.toString(), c])), [components]); - const uniqueScopes = [...new Set(components.map((c) => c.id.scope))]; const { cloudScopes } = useCloudScopes(uniqueScopes); const cloudMap = new Map((cloudScopes || []).map((s) => [s.id.toString(), s])); @@ -52,39 +40,23 @@ export function WorkspaceOverview() { (ScopeID.isValid(component.id.scope) && { id: ScopeID.fromString(component.id.scope) }) || undefined; - return { component, componentDescriptor: descriptor, scope: (scope && { id: scope.id }) || undefined }; + return { + component, + componentDescriptor: descriptor, + scope: scope + ? { id: scope.id, icon: (scope as any).icon, backgroundIconColor: (scope as any).backgroundIconColor } + : undefined, + }; }) ); const [aggregation, setAggregation] = useQueryParamWithDefault('aggregation', 'namespaces'); const [activeNamespaces, setActiveNamespaces] = useListParamWithDefault('ns'); const [activeScopes, setActiveScopes] = useListParamWithDefault('scopes'); - const [density, setDensity] = useQueryParamWithDefault('density', 'comfy'); - - const [statuses, setStatuses] = useState>(() => new Set(ALL_STATUSES)); - - const toggleStatus = useCallback((s: ComponentStatus) => { - setStatuses((prev) => { - const next = new Set(prev); - if (next.has(s)) { - next.delete(s); - if (next.size === 0) return prev; - } else { - next.add(s); - } - return next; - }); - }, []); - - const clearAllFilters = useCallback(() => { - setActiveNamespaces([]); - setActiveScopes([]); - setStatuses(new Set(ALL_STATUSES)); - }, [setActiveNamespaces, setActiveScopes]); const filters = useMemo( - () => ({ namespaces: activeNamespaces, scopes: activeScopes, statuses }), - [activeNamespaces, activeScopes, statuses] + () => ({ namespaces: activeNamespaces, scopes: activeScopes, statuses: new Set() as any }), + [activeNamespaces, activeScopes] ); const { groups, groupType, availableAggregations, filteredCount } = useWorkspaceAggregation( @@ -93,16 +65,6 @@ export function WorkspaceOverview() { filters ); - const cardMin = density === 'compact' ? 220 : 280; - const previewH = density === 'compact' ? 130 : 180; - - const plugins = useCardPlugins({ compModelsById, showPreview: isMinimal }); - - const gridStyle = { - '--card-min': `${cardMin}px`, - '--preview-h': `${previewH}px`, - } as React.CSSProperties; - return (
- {filteredCount === 0 && } - - {groups.map((group) => ( -
- {groupType !== 'none' && } - - - {group.items.map((item) => ( - - ))} - -
- ))} -
- ); -} - -export function useCardPlugins({ - compModelsById, - showPreview, -}: { - compModelsById: Map; - showPreview?: boolean; -}): ComponentCardPluginType[] { - const serverUrlsSignature = React.useMemo(() => { - const serversCount = Array.from(compModelsById.values()) - .filter((comp) => comp.server?.url) - .map((comp) => comp.server?.url) - .join(','); - return serversCount; - }, [compModelsById]); - - const plugins = React.useMemo( - () => [ - { - preview: function Preview({ component, shouldShowPreview }) { - const compModel = compModelsById.get(component.id.toString()); - if (!compModel) return null; - - const item = { component: compModel } as any; - const status = getComponentStatus(item); - const ns = compModel.id.namespace || '/'; - const accent = getAccent(ns); - - if (status === 'queued') return ; - if (status === 'building') return ; - - return ( - - ); - }, - }, - { - previewBottomRight: function PreviewBottomRight({ component }) { - const compModel = compModelsById.get(component.id.toString()); - if (!compModel) return null; - - const item = { component: compModel } as any; - const status = getComponentStatus(item); - if (status === 'queued') return null; - - const env = component.get('teambit.envs/envs'); - const envComponentId = env?.id ? ComponentID.fromString(env?.id) : undefined; - - return ( -
-
- - - +
+ {filteredCount === 0 && } + + {groups.map((group) => ( +
+ {groupType !== 'none' && ( +
+
-
- ); - }, - }, - new LinkPlugin(), - ], - [compModelsById.size, serverUrlsSignature, showPreview] + )} + + + {group.items.map((item) => ( + + ))} + + + ))} +
+
); - - return plugins; } diff --git a/scopes/workspace/workspace/ui/workspace/workspace-overview/workspace-overview.types.ts b/scopes/workspace/workspace/ui/workspace/workspace-overview/workspace-overview.types.ts index fa149c5d5b8f..a101ce8f6d6f 100644 --- a/scopes/workspace/workspace/ui/workspace/workspace-overview/workspace-overview.types.ts +++ b/scopes/workspace/workspace/ui/workspace/workspace-overview/workspace-overview.types.ts @@ -5,7 +5,7 @@ import type { ScopeID } from '@teambit/scopes.scope-id'; export interface WorkspaceItem { component: ComponentModel; componentDescriptor: ComponentDescriptor; - scope?: { id: ScopeID }; + scope?: { id: ScopeID; icon?: string; backgroundIconColor?: string }; } export type AggregationType = 'namespaces' | 'scopes' | 'none'; @@ -18,6 +18,8 @@ export interface AggregationGroup { name: string; displayName: string; items: WorkspaceItem[]; + scopeIcon?: string; + scopeIconColor?: string; } export interface AggregationResult { diff --git a/scopes/workspace/workspace/ui/workspace/workspace.module.scss b/scopes/workspace/workspace/ui/workspace/workspace.module.scss index 9d225d21cd03..0afa5276c4d8 100644 --- a/scopes/workspace/workspace/ui/workspace/workspace.module.scss +++ b/scopes/workspace/workspace/ui/workspace/workspace.module.scss @@ -21,32 +21,23 @@ .minimalCorner { width: fit-content; height: 46px !important; - background: var(--background-color, #ededed) !important; + background: transparent !important; + --bit-border-color-lightest: transparent; &.dark { - --bit-border-color-lightest: #333333; + --bit-border-color-lightest: transparent; --bit-text-color-heavy: white; - - > a > img { - filter: invert(1); - } - } - - &.light { - --background-color: #ededed; } > a { - &[aria-current='page'] > img { - filter: invert(31%) sepia(75%) saturate(3183%) hue-rotate(235deg) brightness(99%) contrast(108%); - } - > img { - width: 22px; - height: 22px; + width: 26px; + height: 26px; + transition: transform 0.15s ease; + cursor: pointer; &:hover { - filter: invert(31%) sepia(75%) saturate(3183%) hue-rotate(235deg) brightness(99%) contrast(108%); + transform: scale(1.1); } } } @@ -96,6 +87,28 @@ flex-direction: column; overflow: hidden; // TODO height: 100vh; + + @media (hover: hover) { + * { + &::-webkit-scrollbar { + width: 14px; + } + + &::-webkit-scrollbar-track { + background: var(--background-color, #fff); + } + + &::-webkit-scrollbar-thumb { + border: 5px solid var(--background-color, #fff); + background: var(--border-medium-color, #d0d0d0); + border-radius: 100vmax; + } + + &::-webkit-scrollbar-thumb:hover { + background: var(--on-background-color, var(--bit-text-color-light, #999)); + } + } + } } .topbar { @@ -135,6 +148,9 @@ &.minimal { height: 46px; + --bit-bg-heaviest: color-mix(in srgb, var(--bit-accent-color, #6c5ce7) 3%, var(--background-color, #fff)); + --bit-border-color-lightest: var(--bit-bg-heaviest); + border-bottom: 1px solid var(--border-medium-color, #e6e6e6); } } diff --git a/scopes/workspace/workspace/ui/workspace/workspace.tsx b/scopes/workspace/workspace/ui/workspace/workspace.tsx index 6f576903edcd..b11fb541a3a5 100644 --- a/scopes/workspace/workspace/ui/workspace/workspace.tsx +++ b/scopes/workspace/workspace/ui/workspace/workspace.tsx @@ -86,6 +86,9 @@ export function Workspace({ routeSlot, menuSlot, sidebar, workspaceUI, onSidebar workspaceUI.setComponents(workspace.components); const inIframe = typeof window !== 'undefined' && window.parent && window.parent !== window; + const location = useLocation(); + const isOverview = location.pathname === '/' || location.pathname === ''; + const showTopBar = !isMinimal || (isMinimal && !isOverview); return ( @@ -94,7 +97,7 @@ export function Workspace({ routeSlot, menuSlot, sidebar, workspaceUI, onSidebar {isMinimal && inIframe && }
- { + {showTopBar && ( ( @@ -102,7 +105,7 @@ export function Workspace({ routeSlot, menuSlot, sidebar, workspaceUI, onSidebar {isMinimal && }
@@ -110,7 +113,7 @@ export function Workspace({ routeSlot, menuSlot, sidebar, workspaceUI, onSidebar // @ts-ignore - getting an error of "Types have separate declarations of a private property 'registerFn'." for some reason after upgrading teambit.harmony/harmony from 0.4.6 to 0.4.7 menu={menuSlot} /> - } + )} {sidebar} From 71ae40c808aa70b297d27c5d15aa77d5e8bd200c Mon Sep 17 00:00:00 2001 From: Luv Kapur Date: Mon, 4 May 2026 11:35:27 -0400 Subject: [PATCH 10/13] clean up --- .../workspace-overview/card-overlays.tsx | 30 ++++++++++--------- .../hope-component-card.tsx | 20 ++++++------- .../workspace-overview/namespace-header.tsx | 5 ++-- .../workspace-overview/namespace-hues.ts | 30 ------------------- 4 files changed, 28 insertions(+), 57 deletions(-) delete mode 100644 scopes/workspace/workspace/ui/workspace/workspace-overview/namespace-hues.ts diff --git a/scopes/workspace/workspace/ui/workspace/workspace-overview/card-overlays.tsx b/scopes/workspace/workspace/ui/workspace/workspace-overview/card-overlays.tsx index 160a297d7164..25e3c84549f6 100644 --- a/scopes/workspace/workspace/ui/workspace/workspace-overview/card-overlays.tsx +++ b/scopes/workspace/workspace/ui/workspace/workspace-overview/card-overlays.tsx @@ -1,6 +1,9 @@ import React from 'react'; import styles from './card-overlays.module.scss'; +const accent = 'var(--bit-accent-color, #6c5ce7)'; +const accentAlpha = (pct: number) => `color-mix(in srgb, var(--bit-accent-color, #6c5ce7) ${pct}%, transparent)`; + export function ChangedPill() { return ( @@ -10,11 +13,11 @@ export function ChangedPill() { ); } -export function BuildSpinner({ accent }: { accent: string }) { +export function BuildSpinner() { return (
- + - - + + - + -
-
-
-
+
+
+
+
); } -export function QueuedPreview({ accent }: { accent: string }) { +export function QueuedPreview() { return (
- - + +
); diff --git a/scopes/workspace/workspace/ui/workspace/workspace-overview/hope-component-card.tsx b/scopes/workspace/workspace/ui/workspace/workspace-overview/hope-component-card.tsx index 91667139cf1d..1be823bb3e95 100644 --- a/scopes/workspace/workspace/ui/workspace/workspace-overview/hope-component-card.tsx +++ b/scopes/workspace/workspace/ui/workspace/workspace-overview/hope-component-card.tsx @@ -8,7 +8,6 @@ import type { ComponentModel } from '@teambit/component'; import type { ComponentDescriptor } from '@teambit/component-descriptor'; import type { ScopeID } from '@teambit/scopes.scope-id'; import { getComponentStatus } from './filter-utils'; -import { getAccent } from './namespace-hues'; import { ChangedPill, BuildSpinner, BuildingPreview, QueuedPreview } from './card-overlays'; import styles from './hope-component-card.module.scss'; @@ -42,8 +41,7 @@ export function HopeComponentCard({ const item = { component } as any; const status = getComponentStatus(item); - const ns = component.id.namespace || '/'; - const accent = getAccent(ns); + const accent = 'var(--bit-accent-color, #6c5ce7)'; const isBuilding = status === 'building'; const isQueued = status === 'queued'; @@ -65,7 +63,12 @@ export function HopeComponentCard({ const envComponentId = env?.id ? ComponentID.fromString(env.id) : undefined; const cardClass = isBuilding ? styles.cardBuilding : styles.card; - const buildingBorderStyle = isBuilding ? { borderColor: accent, boxShadow: `0 0 0 3px ${accent}1A` } : undefined; + const buildingBorderStyle = isBuilding + ? { + borderColor: accent, + boxShadow: `0 0 0 3px color-mix(in srgb, var(--bit-accent-color, #6c5ce7) 10%, transparent)`, + } + : undefined; const nameLabel = component.id.namespace ? `${component.id.namespace}/${component.id.name}` : component.id.name; @@ -84,7 +87,6 @@ export function HopeComponentCard({ component={component} componentDescriptor={componentDescriptor} status={status} - accent={accent} shouldShowPreview={shouldShowPreview} />
@@ -100,7 +102,7 @@ export function HopeComponentCard({ {(isChanged || isBuilding) && (
{isChanged && } - {isBuilding && } + {isBuilding && }
)}
@@ -132,17 +134,15 @@ function CardPreview({ component, componentDescriptor, status, - accent, shouldShowPreview, }: { component: ComponentModel; componentDescriptor: ComponentDescriptor; status: string; - accent: string; shouldShowPreview: boolean; }) { - if (status === 'queued') return ; - if (status === 'building') return ; + if (status === 'queued') return ; + if (status === 'building') return ; return ( = { - actions: 264, - content: 200, - surfaces: 162, - feedback: 38, - inputs: 220, - charts: 142, - navigation: 280, - envs: 250, -}; - -function hashToHue(ns: string): number { - let hash = 0; - for (let i = 0; i < ns.length; i++) { - hash = ((hash << 5) - hash + ns.charCodeAt(i)) | 0; - } - return ((hash % 360) + 360) % 360; -} - -export function getHue(ns: string): number { - return NS_HUES[ns] ?? hashToHue(ns); -} - -export function getAccent(ns: string): string { - return `oklch(58% 0.18 ${getHue(ns)})`; -} - -export function getTint(ns: string): string { - return `oklch(96% 0.04 ${getHue(ns)})`; -} From c724e7cb68f7d82c6c1effc2e0dae4decdd44335 Mon Sep 17 00:00:00 2001 From: Luv Kapur Date: Tue, 5 May 2026 15:36:24 -0400 Subject: [PATCH 11/13] check types fix --- .../workspace-overview/workspace-filter-panel.tsx | 10 ++++++---- .../workspace-overview/workspace-overview.module.scss | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/scopes/workspace/workspace/ui/workspace/workspace-overview/workspace-filter-panel.tsx b/scopes/workspace/workspace/ui/workspace/workspace-overview/workspace-filter-panel.tsx index 1a551c719956..30dfa26e7281 100644 --- a/scopes/workspace/workspace/ui/workspace/workspace-overview/workspace-filter-panel.tsx +++ b/scopes/workspace/workspace/ui/workspace/workspace-overview/workspace-filter-panel.tsx @@ -50,13 +50,15 @@ export function WorkspaceFilterPanel({ const activeNsOptions = activeNamespaces.map((v) => ({ value: v })); const activeScopeOptions = activeScopes.map((v) => ({ value: v })); - const applyNs = (opts: { value?: string }[]) => { - const list = opts.map((o) => o.value).filter((v): v is string => typeof v === 'string'); + const applyNs = (opts: readonly { value?: string }[] | { value?: string } | null) => { + const arr = Array.isArray(opts) ? opts : opts ? [opts] : []; + const list = arr.map((o) => o.value).filter((v): v is string => typeof v === 'string'); onNamespacesChange(list); }; - const applyScopes = (opts: { value?: string }[]) => { - const list = opts.map((o) => o.value).filter((v): v is string => typeof v === 'string'); + const applyScopes = (opts: readonly { value?: string }[] | { value?: string } | null) => { + const arr = Array.isArray(opts) ? opts : opts ? [opts] : []; + const list = arr.map((o) => o.value).filter((v): v is string => typeof v === 'string'); onScopesChange(list); }; diff --git a/scopes/workspace/workspace/ui/workspace/workspace-overview/workspace-overview.module.scss b/scopes/workspace/workspace/ui/workspace/workspace-overview/workspace-overview.module.scss index dd9463aca5de..ee9793739b69 100644 --- a/scopes/workspace/workspace/ui/workspace/workspace-overview/workspace-overview.module.scss +++ b/scopes/workspace/workspace/ui/workspace/workspace-overview/workspace-overview.module.scss @@ -10,7 +10,7 @@ .content { max-width: 1320px; margin: 0 auto; - padding: 0 40px 80px; + padding: 20px 40px 80px; } /* ---- Command bar ---- */ From 4889d0b3ea230a0c910f3c90a5e8e9ed5d6f5367 Mon Sep 17 00:00:00 2001 From: Luv Kapur Date: Fri, 8 May 2026 12:18:05 -0400 Subject: [PATCH 12/13] clean up --- .../hope-component-card.tsx | 10 +++--- .../namespace-header.module.scss | 13 +++---- .../workspace-overview.module.scss | 2 -- .../ui/workspace/workspace.module.scss | 34 +++++++------------ .../workspace/ui/workspace/workspace.tsx | 24 ++++++++++--- 5 files changed, 41 insertions(+), 42 deletions(-) diff --git a/scopes/workspace/workspace/ui/workspace/workspace-overview/hope-component-card.tsx b/scopes/workspace/workspace/ui/workspace/workspace-overview/hope-component-card.tsx index 1be823bb3e95..4aeac2ac1bb5 100644 --- a/scopes/workspace/workspace/ui/workspace/workspace-overview/hope-component-card.tsx +++ b/scopes/workspace/workspace/ui/workspace/workspace-overview/hope-component-card.tsx @@ -8,7 +8,7 @@ import type { ComponentModel } from '@teambit/component'; import type { ComponentDescriptor } from '@teambit/component-descriptor'; import type { ScopeID } from '@teambit/scopes.scope-id'; import { getComponentStatus } from './filter-utils'; -import { ChangedPill, BuildSpinner, BuildingPreview, QueuedPreview } from './card-overlays'; +import { ChangedPill, BuildSpinner, BuildingPreview } from './card-overlays'; import styles from './hope-component-card.module.scss'; export type HopeComponentCardProps = { @@ -49,8 +49,7 @@ export function HopeComponentCard({ const href = `${component.id.fullName}?scope=${component.id.scope}`; - const loadPreviewVisible = - component.compositions.length > 0 && component.buildStatus !== 'pending' && !shouldShowPreview; + const loadPreviewVisible = component.compositions.length > 0 && !isBuilding && !shouldShowPreview; const showPreviewClick = (e: React.MouseEvent) => { e.stopPropagation(); @@ -81,7 +80,7 @@ export function HopeComponentCard({ {loadPreviewVisible && } -
+
- {nameLabel} + {nameLabel} {!isBuilding && !isQueued && shortHash && {shortHash}} {isBuilding && ( @@ -141,7 +140,6 @@ function CardPreview({ status: string; shouldShowPreview: boolean; }) { - if (status === 'queued') return ; if (status === 'building') return ; return ( diff --git a/scopes/workspace/workspace/ui/workspace/workspace-overview/namespace-header.module.scss b/scopes/workspace/workspace/ui/workspace/workspace-overview/namespace-header.module.scss index 257ffd4c3331..1aa1b6e5b137 100644 --- a/scopes/workspace/workspace/ui/workspace/workspace-overview/namespace-header.module.scss +++ b/scopes/workspace/workspace/ui/workspace/workspace-overview/namespace-header.module.scss @@ -12,18 +12,15 @@ } .name { - font-family: 'JetBrains Mono', ui-monospace, Menlo, monospace; - font-size: 13px; + font-size: 15px; font-weight: 600; - letter-spacing: 0.1em; - text-transform: uppercase; + letter-spacing: 0.02em; color: var(--on-background-color); flex-shrink: 0; } .count { - font-family: 'JetBrains Mono', ui-monospace, Menlo, monospace; - font-size: 11px; + font-size: 12px; color: var(--on-background-low-color); flex-shrink: 0; } @@ -48,8 +45,8 @@ } .scopeIconBadge { - width: 22px; - height: 22px; + width: 24px; + height: 24px; border-radius: 6px; display: flex; align-items: center; diff --git a/scopes/workspace/workspace/ui/workspace/workspace-overview/workspace-overview.module.scss b/scopes/workspace/workspace/ui/workspace/workspace-overview/workspace-overview.module.scss index ee9793739b69..3cf92249f348 100644 --- a/scopes/workspace/workspace/ui/workspace/workspace-overview/workspace-overview.module.scss +++ b/scopes/workspace/workspace/ui/workspace/workspace-overview/workspace-overview.module.scss @@ -8,8 +8,6 @@ } .content { - max-width: 1320px; - margin: 0 auto; padding: 20px 40px 80px; } diff --git a/scopes/workspace/workspace/ui/workspace/workspace.module.scss b/scopes/workspace/workspace/ui/workspace/workspace.module.scss index 0afa5276c4d8..c32b0b00404e 100644 --- a/scopes/workspace/workspace/ui/workspace/workspace.module.scss +++ b/scopes/workspace/workspace/ui/workspace/workspace.module.scss @@ -18,28 +18,20 @@ } } -.minimalCorner { - width: fit-content; - height: 46px !important; - background: transparent !important; - --bit-border-color-lightest: transparent; - - &.dark { - --bit-border-color-lightest: transparent; - --bit-text-color-heavy: white; - } - - > a { - > img { - width: 26px; - height: 26px; - transition: transform 0.15s ease; - cursor: pointer; +.backButton { + display: flex; + align-items: center; + justify-content: center; + width: 32px; + height: 32px; + border-radius: 8px; + color: var(--on-background-color, var(--bit-text-color-heavy, #2b2b2b)); + text-decoration: none; + transition: background 0.15s ease; + margin-left: 8px; - &:hover { - transform: scale(1.1); - } - } + &:hover { + background: color-mix(in srgb, var(--on-background-color, #2b2b2b) 8%, transparent); } } diff --git a/scopes/workspace/workspace/ui/workspace/workspace.tsx b/scopes/workspace/workspace/ui/workspace/workspace.tsx index b11fb541a3a5..0b461e8454dd 100644 --- a/scopes/workspace/workspace/ui/workspace/workspace.tsx +++ b/scopes/workspace/workspace/ui/workspace/workspace.tsx @@ -102,11 +102,25 @@ export function Workspace({ routeSlot, menuSlot, sidebar, workspaceUI, onSidebar className={classNames(styles.topbar, styles[themeName], isMinimal && styles.minimal)} Corner={() => (
- + {isMinimal ? ( + + + + + + ) : ( + + )} {isMinimal && }
)} From 105e6ad59ae6fe6d555355490bed40af77d7ad9b Mon Sep 17 00:00:00 2001 From: Luv Kapur Date: Fri, 8 May 2026 12:51:35 -0400 Subject: [PATCH 13/13] clean up --- .../workspace/workspace-overview/namespace-header.module.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scopes/workspace/workspace/ui/workspace/workspace-overview/namespace-header.module.scss b/scopes/workspace/workspace/ui/workspace/workspace-overview/namespace-header.module.scss index 1aa1b6e5b137..da455d470942 100644 --- a/scopes/workspace/workspace/ui/workspace/workspace-overview/namespace-header.module.scss +++ b/scopes/workspace/workspace/ui/workspace/workspace-overview/namespace-header.module.scss @@ -12,7 +12,7 @@ } .name { - font-size: 15px; + font-size: 20px; font-weight: 600; letter-spacing: 0.02em; color: var(--on-background-color);