Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions scopes/docs/docs/overview/overview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ export function Overview({
viewport={null}
fullContentHeight
disableScroll={true}
propagateError={isMinimal}
sandbox={sandboxValue}
{...rest}
component={component}
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
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 (
<span className={styles.changedPill}>
<span className={styles.changedDot} />
changed
</span>
);
}

export function BuildSpinner() {
return (
<div className={styles.spinnerBadge}>
<svg width="14" height="14" viewBox="0 0 14 14">
<circle cx="7" cy="7" r="5" stroke={accentAlpha(20)} strokeWidth="1.5" fill="none" />
<circle
cx="7"
cy="7"
r="5"
stroke={accent}
strokeWidth="1.5"
fill="none"
strokeDasharray="8 24"
strokeLinecap="round"
className={styles.spinnerArc}
/>
</svg>
</div>
);
}

export function BuildingPreview() {
return (
<div className={styles.buildingPreview}>
<svg width="100%" height="100%" preserveAspectRatio="none" className={styles.dotsPattern}>
<defs>
<pattern id="dots-accent" width="14" height="14" patternUnits="userSpaceOnUse">
<circle cx="7" cy="7" r="1" fill={accentAlpha(20)} />
</pattern>
</defs>
<rect width="100%" height="100%" fill="url(#dots-accent)" />
</svg>
<div className={styles.buildingPlaceholder} style={{ border: `1.5px dashed ${accentAlpha(40)}` }}>
<div style={{ height: 6, width: '55%', background: accentAlpha(27), borderRadius: 3 }} />
<div style={{ height: 4, width: '85%', background: accentAlpha(13), borderRadius: 2 }} />
<div style={{ height: 4, width: '70%', background: accentAlpha(13), borderRadius: 2 }} />
</div>
</div>
);
}

export function QueuedPreview() {
return (
<div className={styles.queuedPreview}>
<svg width="46" height="46" viewBox="0 0 46 46">
<circle cx="23" cy="23" r="20" fill="none" stroke={accentAlpha(20)} strokeWidth="1.2" strokeDasharray="3 4" />
<circle cx="23" cy="23" r="3" fill={accentAlpha(53)} />
</svg>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -1,24 +1,41 @@
import type { WorkspaceItem } from './workspace-overview.types';
import type { WorkspaceItem, ComponentStatus } from './workspace-overview.types';

export interface ActiveFilters {
namespaces: string[];
scopes: string[];
statuses: Set<ComponentStatus>;
}

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 || '/';
const sc = item.component.id.scope;

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;
});
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
Loading