Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .claude/rules/emcn-components.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Import components, `cn`, and tokens from the `@sim/emcn` barrel; icons come from

Never hand-roll the chip pill from raw class strings (they go stale). Compose from the canonical sources:

- **Surface, typography + content tokens:** `chip/chip-chrome.ts` — `chipFilledSurfaceTokens`, `chipFieldSurfaceClass`, `chipFieldTextClass` (text fields and the dropdown search box build on these), plus the chip-content chrome `chipContentGap`, `chipGeometryClass`, `chipContentIconClass`, `chipContentLabelClass`, and `cellIconNodeClass` (non-chip surfaces that must visually match chip content, e.g. resource table cells). All are re-exported from the `@sim/emcn` barrel — no subpath import needed.
- **Surface, typography + content tokens:** `chip/chip-chrome.ts` — `chipFilledSurfaceTokens`, `chipFieldSurfaceClass`, `chipFieldTextClass` (text fields and the dropdown search box build on these), plus the chip-content chrome `chipContentGap`, `chipGeometryClass`, `chipContentIconClass`, `chipContentLabelClass`, `cellIconNodeClass` (non-chip surfaces that must visually match chip content, e.g. resource table cells), and the row-state pair `chipHoverSurfaceClass` / `chipActiveSurfaceClass` (hover vs. selected — mutually exclusive, so a selected row holds its surface through hover; every hand-rolled row imports these rather than restating the literals). All are re-exported from the `@sim/emcn` barrel — no subpath import needed.
- **Pill geometry:** `chip/chip.tsx` — `chipVariants` (30px tall, `rounded-lg`, `px-2`, icon↔text `gap-1.5`). Every pill-shaped trigger (`ChipDropdown`, `ChipSelect`, `ChipSwitch`) reuses it for visual parity.

Canonical look: normal font-weight (never `font-medium`/`font-semibold`), value text `--text-body`, icons `--text-icon` at `size-[14px]`, placeholder `--text-muted`, `transition-colors`, **no focus ring** (the caret marks focus). Filled surface is `--surface-5` light / `--surface-4` dark with a `--border-1` border.
Expand Down
16 changes: 5 additions & 11 deletions apps/docs/app/global.css
Original file line number Diff line number Diff line change
Expand Up @@ -518,19 +518,13 @@ html #nd-sidebar button[data-active="true"] {
color: var(--text-body) !important;
}

/* Hover state — the app's nav Chip hovers to --surface-active. These rules must
carry !important to beat fumadocs' own sidebar styles, which also means they
override the Tailwind hover utilities on the items; keep the two in step. */
/* Hover state — emcn's two-surface row model (see `chipHoverSurfaceClass`).
These rules must carry !important to beat fumadocs' own sidebar styles, which
also means they override the Tailwind hover utilities on the items; keep the
two in step. */
html #nd-sidebar a:not(:has(span.font-mono)):hover:not([data-active="true"]),
html #nd-sidebar button:hover:not([data-active="true"]) {
background-color: var(--surface-active) !important;
}

/* An active item darkens one surface on hover, matching the app's active Chip */
html #nd-sidebar a[data-active="true"]:not(:has(span.font-mono)):hover,
html #nd-sidebar button[data-active="true"]:hover {
background-color: var(--surface-6) !important;
color: var(--text-body) !important;
background-color: var(--surface-hover) !important;
Comment thread
waleedlatif1 marked this conversation as resolved.
}

/* Hide search, platform, and collapse button from sidebar completely */
Expand Down
59 changes: 22 additions & 37 deletions apps/docs/components/docs-layout/sidebar-components.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use client'

import { type ReactNode, useState } from 'react'
import { chipActiveSurfaceClass, chipHoverSurfaceClass } from '@sim/emcn'
import { ChevronRight } from '@sim/emcn/icons'
import type { Folder, Item, Separator } from 'fumadocs-core/page-tree'
import { useSidebar } from 'fumadocs-ui/components/sidebar/base'
Expand Down Expand Up @@ -43,7 +44,7 @@ function isActive(url: string, pathname: string, nested = true): boolean {
/**
* Rows mirror the app sidebar's chip pill: 30px tall, `rounded-lg`, `px-2`, 14px
* at normal weight, `--text-body` at rest AND when active — only the background
* moves, to `--surface-active`, then `--surface-6` when an active row is hovered.
* moves, on the two-surface model — see emcn's `chipHoverSurfaceClass`.
*
* Height, horizontal padding, weight and color are additionally pinned in
* `global.css` (`html #nd-sidebar a…`), which needs `!important` to beat
Expand All @@ -52,40 +53,33 @@ function isActive(url: string, pathname: string, nested = true): boolean {
* the stylesheet is what actually lands on desktop.
*/
const ITEM_BASE =
'flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-[var(--text-body)] text-sm transition-colors hover:bg-[var(--surface-active)]'
const ITEM_ACTIVE_MOBILE = 'bg-[var(--surface-active)]'
'flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-[var(--text-body)] text-sm transition-colors'
const ITEM_ACTIVE_MOBILE = chipActiveSurfaceClass

const ITEM_DESKTOP =
'lg:mb-[0.0625rem] lg:block lg:rounded-lg lg:px-2 lg:font-normal lg:text-sm lg:leading-tight'
const ITEM_TEXT = 'lg:text-[var(--text-body)]'
const ITEM_HOVER = 'lg:hover:bg-[var(--surface-active)]'
const ITEM_ACTIVE =
'lg:bg-[var(--surface-active)] lg:font-normal lg:text-[var(--text-body)] lg:hover:bg-[var(--surface-6)]'
/**
* Unprefixed, and applied only to inactive rows — an unconditional hover in
* `ITEM_BASE` would fade the current page under the pointer below `lg`.
*/
const ITEM_HOVER = chipHoverSurfaceClass
const ITEM_ACTIVE = 'lg:bg-[var(--surface-active)] lg:font-normal lg:text-[var(--text-body)]'

const FOLDER_TEXT = 'lg:text-[var(--text-body)] lg:font-normal'
const FOLDER_HOVER = 'lg:hover:bg-[var(--surface-active)]'
const FOLDER_ACTIVE =
'lg:bg-[var(--surface-active)] lg:text-[var(--text-body)] lg:hover:bg-[var(--surface-6)]'
const FOLDER_HOVER = chipHoverSurfaceClass
const FOLDER_ACTIVE = 'lg:bg-[var(--surface-active)] lg:text-[var(--text-body)]'

const itemClass = (active: boolean) =>
cn(ITEM_BASE, ITEM_DESKTOP, ITEM_TEXT, active ? cn(ITEM_ACTIVE_MOBILE, ITEM_ACTIVE) : ITEM_HOVER)

export function SidebarItem({ item }: { item: Item }) {
const pathname = usePathname()
const { prefetch } = useSidebar()
const active = isActive(item.url, pathname, false)

return (
<Link
href={item.url}
prefetch={prefetch}
data-active={active}
className={cn(
ITEM_BASE,
active && ITEM_ACTIVE_MOBILE,
ITEM_DESKTOP,
ITEM_TEXT,
!active && ITEM_HOVER,
active && ITEM_ACTIVE
)}
>
<Link href={item.url} prefetch={prefetch} data-active={active} className={itemClass(active)}>
{item.name}
</Link>
)
Expand Down Expand Up @@ -119,14 +113,7 @@ export function SidebarFolder({ item, children }: { item: Folder; children: Reac
href={item.index.url}
prefetch={prefetch}
data-active={active}
className={cn(
ITEM_BASE,
active && ITEM_ACTIVE_MOBILE,
ITEM_DESKTOP,
ITEM_TEXT,
!active && ITEM_HOVER,
active && ITEM_ACTIVE
)}
className={itemClass(active)}
>
{item.name}
</Link>
Expand All @@ -144,12 +131,10 @@ export function SidebarFolder({ item, children }: { item: Folder; children: Reac
data-active={active}
className={cn(
'flex flex-1 items-center gap-2 rounded-md px-2 py-1.5 text-sm transition-colors',
'text-[var(--text-body)] hover:bg-[var(--surface-active)]',
active && ITEM_ACTIVE_MOBILE,
'text-[var(--text-body)]',
'lg:block lg:flex-1 lg:rounded-lg lg:px-2 lg:text-sm lg:leading-tight',
FOLDER_TEXT,
!active && FOLDER_HOVER,
active && FOLDER_ACTIVE
active ? cn(ITEM_ACTIVE_MOBILE, FOLDER_ACTIVE) : FOLDER_HOVER
)}
>
{item.name}
Expand All @@ -158,8 +143,8 @@ export function SidebarFolder({ item, children }: { item: Folder; children: Reac
<button
onClick={toggleOpen}
className={cn(
'rounded-md p-1 hover:bg-[var(--surface-active)]',
'lg:cursor-pointer lg:rounded-md lg:p-1 lg:transition-colors lg:hover:bg-[var(--surface-active)]'
'rounded-md p-1 transition-colors lg:cursor-pointer',
chipHoverSurfaceClass
)}
aria-label={open ? 'Collapse' : 'Expand'}
>
Expand All @@ -172,7 +157,7 @@ export function SidebarFolder({ item, children }: { item: Folder; children: Reac
onClick={toggleOpen}
className={cn(
'flex flex-1 items-center gap-2 rounded-md px-2 py-1.5 text-sm transition-colors',
'text-[var(--text-body)] hover:bg-[var(--surface-active)]',
'text-[var(--text-body)]',
'lg:flex lg:w-full lg:cursor-pointer lg:items-center lg:justify-between lg:rounded-lg lg:px-2 lg:text-left lg:text-sm lg:leading-tight',
FOLDER_TEXT,
FOLDER_HOVER
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
'use client'
import { ChevronDown, cn, Home, Library } from '@sim/emcn'
import {
ChevronDown,
chipActiveSurfaceClass,
chipHoverSurfaceClass,
cn,
Home,
Library,
} from '@sim/emcn'
import { Database, File, HelpCircle, Search, Settings, Table, Workflow } from '@sim/emcn/icons'
import type { PreviewWorkflow } from '@/app/(landing)/components/landing-preview/components/landing-preview-workflow/workflow-data'

Expand Down Expand Up @@ -51,8 +58,8 @@ function NavItem({
type='button'
onClick={onClick}
className={cn(
'mx-0.5 flex h-[28px] items-center gap-2 rounded-[8px] px-2 transition-colors hover-hover:bg-[var(--c-active)]',
isActive && 'bg-[var(--c-active)]'
'mx-0.5 flex h-[28px] items-center gap-2 rounded-[8px] px-2 transition-colors',
isActive ? chipActiveSurfaceClass : chipHoverSurfaceClass
)}
>
<Icon className='size-[14px] flex-shrink-0 text-[var(--text-icon)]' />
Expand All @@ -77,10 +84,7 @@ export function LandingPreviewSidebar({
const isHomeActive = activeView === 'home'

return (
<div
className='flex h-full w-[248px] flex-shrink-0 flex-col bg-[var(--surface-1)] pt-3'
style={{ '--c-active': 'var(--surface-active)' } as React.CSSProperties}
>
<div className='flex h-full w-[248px] flex-shrink-0 flex-col bg-[var(--surface-1)] pt-3'>
{/* Workspace Header */}
<div className='flex-shrink-0 px-2.5'>
<div className='pointer-events-none flex h-[32px] w-full items-center gap-2 rounded-[8px] border border-[var(--border-1)] bg-[var(--surface-2)] pr-2 pl-[5px]'>
Expand All @@ -107,8 +111,8 @@ export function LandingPreviewSidebar({
type='button'
onClick={onSelectHome}
className={cn(
'mx-0.5 flex h-[28px] items-center gap-2 rounded-[8px] px-2 transition-colors hover-hover:bg-[var(--c-active)]',
isHomeActive && 'bg-[var(--c-active)]'
'mx-0.5 flex h-[28px] items-center gap-2 rounded-[8px] px-2 transition-colors',
isHomeActive ? chipActiveSurfaceClass : chipHoverSurfaceClass
)}
>
<Home className='size-[14px] flex-shrink-0 text-[var(--text-icon)]' />
Expand Down Expand Up @@ -151,8 +155,8 @@ export function LandingPreviewSidebar({
type='button'
onClick={() => onSelectWorkflow(workflow.id)}
className={cn(
'mx-0.5 flex h-[28px] w-full items-center gap-2 rounded-[8px] px-2 transition-colors hover-hover:bg-[var(--surface-active)]',
isActive && 'bg-[var(--surface-active)]'
'mx-0.5 flex h-[28px] w-full items-center gap-2 rounded-[8px] px-2 transition-colors',
isActive ? chipActiveSurfaceClass : chipHoverSurfaceClass
)}
>
<Workflow className='size-[14px] flex-shrink-0 text-[var(--text-icon)]' />
Expand Down
20 changes: 20 additions & 0 deletions apps/sim/app/_styles/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,18 @@
--panel-width: 320px; /* PANEL_WIDTH.DEFAULT */
--editor-connections-height: 172px; /* EDITOR_CONNECTIONS_HEIGHT.DEFAULT */
--terminal-height: 206px; /* TERMINAL_HEIGHT.DEFAULT */
/**
* The padding `.workspace-content-shell` insets the panel and terminal from
* the viewport by (CONTENT_WINDOW_GAP).
*
* Published here because surfaces portalled to `<body>` — the toast stack —
* position against those elements from the viewport, so they must add back
* whatever separates the element from the viewport edge. Reading it rather
* than hardcoding 8px is what keeps the toast and the canvas controls on the
* same clearance when the shell drops its padding; the controls are laid out
* inside the shell and so need no correction.
*/
--workspace-content-gap: 8px;
--output-panel-width: 560px; /* OUTPUT_PANEL_WIDTH.DEFAULT */
/**
* Neutral border and divider thickness. Standard-density displays cannot draw
Expand Down Expand Up @@ -162,6 +174,14 @@ html[data-sim-desktop-title-bar="inset"]
--workspace-content-title-bar-inset: var(--desktop-title-bar-height);
}

/* The one case the shell drops its padding entirely (see `workspace-chrome.tsx`:
`isCollapsed && '[[data-sim-desktop-title-bar=inset]_&]:p-0'`). Declared on the
root so the portalled toast stack — which cannot inherit from the shell — sees
it too, and keeps the same clearance the in-shell canvas controls keep. */
html[data-sim-desktop-title-bar="inset"]:has(.workspace-content-shell[data-sidebar-collapsed]) {
--workspace-content-gap: 0px;
}

.workspace-root code,
.workspace-root kbd,
.workspace-root samp,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use client'

import { useCallback, useRef, useState } from 'react'
import { cn, Tooltip } from '@sim/emcn'
import { chipActiveSurfaceClass, chipHoverSurfaceClass, cn, Tooltip } from '@sim/emcn'
import { ArrowUp, ChevronDown, ChevronRight, Paperclip, Pencil, Trash, X } from '@sim/emcn/icons'
import { UserMessageContent } from '@/app/workspace/[workspaceId]/home/components/user-message-content'
import type { QueuedMessage } from '@/app/workspace/[workspaceId]/home/types'
Expand Down Expand Up @@ -54,7 +54,10 @@ export function QueuedMessages({
<button
type='button'
onClick={() => setIsExpanded(!isExpanded)}
className='flex w-full items-center gap-1.5 px-3.5 py-2 transition-colors hover-hover:bg-[var(--surface-active)]'
className={cn(
'flex w-full items-center gap-1.5 px-3.5 py-2 transition-colors',
chipHoverSurfaceClass
)}
>
{isExpanded ? (
<ChevronDown className='size-[14px] text-[var(--text-icon)]' />
Expand All @@ -75,8 +78,8 @@ export function QueuedMessages({
<div
key={msg.id}
className={cn(
'flex items-center gap-2 py-1.5 pr-2 pl-3.5 transition-colors hover-hover:bg-[var(--surface-active)]',
isEditing && 'bg-[var(--surface-active)]'
'flex items-center gap-2 py-1.5 pr-2 pl-3.5 transition-colors',
isEditing ? chipActiveSurfaceClass : chipHoverSurfaceClass
)}
>
<div className='flex size-[16px] shrink-0 items-center justify-center'>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { useCallback, useEffect, useMemo, useState } from 'react'
import {
Badge,
Button,
Chip,
ChipCombobox,
ChipInput,
type ComboboxOption,
Expand Down Expand Up @@ -506,9 +506,9 @@ export function McpDeploy({
<p className='text-[var(--text-muted)] text-small'>
Create an MCP Server to expose your workflows as tools.
</p>
<Button variant='tertiary' onClick={() => setShowCreateModal(true)}>
<Chip variant='primary' onClick={() => setShowCreateModal(true)}>
Create MCP Server
</Button>
</Chip>
</div>
<CreateWorkflowMcpServerModal
open={showCreateModal}
Expand Down
Loading
Loading