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 @@ -32,7 +32,7 @@ The menu surface intentionally diverges from the pill: `dropdown-menu.tsx` items
- **`ChipDatePicker`** — chip-styled date field.
- **`ChipTimePicker`** — minute-granular time sibling of `ChipDatePicker`, a `ChipInput` that leniently parses typed input (`9:47`, `947`, `2:05pm`, `14:30`), commits on Enter/blur, and re-renders the canonical `9:47 AM` label.
- **`DropdownMenu`** — the canonical context/action menu (Radix-backed). Not a chip, but the standard menu for command/action lists; reach for it instead of a hand-rolled popover. Its surface intentionally diverges from the chip pill (`text-small`, `gap-2`) — keep them distinct. For a pill that opens a value picker, use `ChipDropdown`/`ChipSelect` instead.
- **`OverflowText`** — the canonical single-line overflow treatment for read-only human labels and titles. It owns `min-w-0`, single-line clipping, the conditional 18px edge fade, and the full-value floating tooltip; consumers pass only layout/typography through `className`. Never combine the fade with `truncate`, which paints an ellipsis beneath the mask. Keep ordinary `truncate` for editable or mirrored input values, code/log/path content, dense or virtualized grids, and composite rows where masking the container would also fade icons or actions. Multiline copy uses an intentional `line-clamp-*` treatment instead. A non-editable `Combobox` visual overlay passes its plain value through `overlayLabel`; render its visible `OverflowText` as a constrained block with `tooltipEnabled={false}` so the interactive trigger owns the single accessible tooltip.
- **`OverflowText`** — the canonical single-line overflow treatment for read-only human labels and titles. It owns `min-w-0`, fade-only clipping (never an ellipsis), the conditional 18px edge mask, and the full-value floating tooltip; consumers pass only layout/typography through `className`. `overflowTextClipClass` and `overflowTextFadeClass` are the complete base/faded treatments for the rare component that must own measurement itself; never pair either with `truncate`, `text-ellipsis`, or hover-time mask removal. Use `DropdownMenuItemLabel` for a menu label beside icons, checks, or actions. A non-editable `Combobox` passes the full visual value through `overlayLabel`; the combobox owns the visual overlay's fade and keeps its one accessible tooltip on the interactive layer. Keep ordinary `truncate` only for editable values, code/log/path content, dense or virtualized grids, and rich composite content that cannot supply a plain tooltip label. Multiline copy uses an intentional `line-clamp-*` treatment instead.

## Modal keyboard defaults

Expand Down
8 changes: 5 additions & 3 deletions .claude/rules/sim-styling.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,11 @@ Icons default `size-[14px]`. Equal h/w → `size-*` (`size-[14px]`, `size-4`), n

## Text Overflow

Use `OverflowText` from `@sim/emcn` for a constrained, single-line, read-only human label or title. It owns `min-w-0`, single-line clipping, the conditional edge fade, and the full-value floating tooltip; pass only layout and typography through `className`. Never combine a fade or hand-written `mask-image` with `truncate`, which leaves an ellipsis beneath the mask. Pass the full label to this component instead of shortening it in JavaScript first.
Use `OverflowText` from `@sim/emcn` for a constrained, single-line, read-only human label or title. It owns `min-w-0`, fade-only clipping, the conditional edge mask, and the full-value floating tooltip; pass only layout and typography through `className`. Never combine a fade or hand-written `mask-image` with `truncate`/`text-ellipsis`, and never remove the mask on hover to reveal an ellipsis. Pass the full label instead of shortening it in JavaScript first. Components that must measure a label externally use the complete `overflowTextClipClass` + conditional `overflowTextFadeClass` pair.

For a non-editable `Combobox` visual overlay, pass the same plain value as `overlayLabel` and render the visible `OverflowText` with `block w-full` (or `block flex-1` beside an icon) plus `tooltipEnabled={false}`. The transparent interactive layer then owns the one reachable full-value tooltip while the visual layer owns the measured fade.
For a non-editable `Combobox` visual overlay, pass the same full plain value as `overlayLabel`. The combobox owns the visible overlay's fade and keeps the one reachable full-value tooltip on its interactive layer; consumers provide only the overlay's decorated content.

Use `DropdownMenuItemLabel` for a human label beside menu icons, checks, shortcuts, or actions. Bare string children are wrapped automatically; a direct rich `<span>` is only a hard-clipped escape hatch and must not be used for an ordinary text label.

Do not apply the fade universally to editable or mirrored input values, code, logs, paths, filenames that use intentional middle truncation, dense or virtualized grids, or a composite container that also holds icons/actions. Those keep their purpose-built overflow behavior. Multiline copy uses an intentional `line-clamp-*` treatment.

Expand Down Expand Up @@ -92,7 +94,7 @@ Draw a line with a real `border-*` utility. Never hand-roll one as `shadow-[inse

### What className MAY carry

Layout/sizing ONLY: `flex-1`, `w-full`, `w-[Npx]`, `min-w-0`, `max-w-*`, margins, `truncate`. Example: `<ChipInput icon={Search} className='min-w-0 flex-1' .../>` (`app/workspace/[workspaceId]/integrations/integrations.tsx:257`). NEVER re-specify canonical chrome — the component already applies it.
Layout/sizing ONLY: `flex-1`, `w-full`, `w-[Npx]`, `min-w-0`, `max-w-*`, margins. `truncate` is allowed only for the explicit overflow exceptions above, never as a general layout class. Example: `<ChipInput icon={Search} className='min-w-0 flex-1' .../>` (`app/workspace/[workspaceId]/integrations/integrations.tsx:257`). NEVER re-specify canonical chrome — the component already applies it.

### Form / chip-modal layout rhythm

Expand Down
8 changes: 5 additions & 3 deletions .cursor/rules/sim-styling.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,11 @@ Icons default `size-[14px]`. Equal h/w → `size-*` (`size-[14px]`, `size-4`), n

## Text Overflow

Use `OverflowText` from `@sim/emcn` for a constrained, single-line, read-only human label or title. It owns `min-w-0`, single-line clipping, the conditional edge fade, and the full-value floating tooltip; pass only layout and typography through `className`. Never combine a fade or hand-written `mask-image` with `truncate`, which leaves an ellipsis beneath the mask. Pass the full label to this component instead of shortening it in JavaScript first.
Use `OverflowText` from `@sim/emcn` for a constrained, single-line, read-only human label or title. It owns `min-w-0`, fade-only clipping, the conditional edge mask, and the full-value floating tooltip; pass only layout and typography through `className`. Never combine a fade or hand-written `mask-image` with `truncate`/`text-ellipsis`, and never remove the mask on hover to reveal an ellipsis. Pass the full label instead of shortening it in JavaScript first. Components that must measure a label externally use the complete `overflowTextClipClass` + conditional `overflowTextFadeClass` pair.

For a non-editable `Combobox` visual overlay, pass the same plain value as `overlayLabel` and render the visible `OverflowText` with `block w-full` (or `block flex-1` beside an icon) plus `tooltipEnabled={false}`. The transparent interactive layer then owns the one reachable full-value tooltip while the visual layer owns the measured fade.
For a non-editable `Combobox` visual overlay, pass the same full plain value as `overlayLabel`. The combobox owns the visible overlay's fade and keeps the one reachable full-value tooltip on its interactive layer; consumers provide only the overlay's decorated content.

Use `DropdownMenuItemLabel` for a human label beside menu icons, checks, shortcuts, or actions. Bare string children are wrapped automatically; a direct rich `<span>` is only a hard-clipped escape hatch and must not be used for an ordinary text label.

Do not apply the fade universally to editable or mirrored input values, code, logs, paths, filenames that use intentional middle truncation, dense or virtualized grids, or a composite container that also holds icons/actions. Those keep their purpose-built overflow behavior. Multiline copy uses an intentional `line-clamp-*` treatment.

Expand All @@ -70,7 +72,7 @@ Value text `--text-body`; muted/placeholder/labels `--text-muted`; icons `--text

### What className MAY carry

Layout/sizing ONLY: `flex-1`, `w-full`, `w-[Npx]`, `min-w-0`, `max-w-*`, margins, `truncate`. Example: `<ChipInput icon={Search} className='min-w-0 flex-1' .../>` (`app/workspace/[workspaceId]/integrations/integrations.tsx:257`). NEVER re-specify canonical chrome — the component already applies it.
Layout/sizing ONLY: `flex-1`, `w-full`, `w-[Npx]`, `min-w-0`, `max-w-*`, margins. `truncate` is allowed only for the explicit overflow exceptions above, never as a general layout class. Example: `<ChipInput icon={Search} className='min-w-0 flex-1' .../>` (`app/workspace/[workspaceId]/integrations/integrations.tsx:257`). NEVER re-specify canonical chrome — the component already applies it.

### Form / chip-modal layout rhythm

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
DropdownMenuTrigger,
FloatingTooltip,
OverflowText,
overflowTextClipClass,
overflowTextFadeClass,
POPOVER_ANIMATION_CLASSES,
Popover,
Expand Down Expand Up @@ -543,9 +544,11 @@ function BreadcrumbLocationPopover({
<ArrowUpLeft className='col-start-1 row-start-1 size-[16px] scale-[0.25] text-[var(--text-icon)] opacity-0 blur-[2px] transition-[opacity,filter,transform] duration-200 ease-in-out group-hover:scale-100 group-hover:opacity-100 group-hover:blur-0 group-focus-visible:scale-100 group-focus-visible:opacity-100 group-focus-visible:blur-0 motion-reduce:transition-none' />
</span>
{rootBreadcrumb?.label && (
<span className='shrink-0 truncate text-[var(--text-body)] text-sm'>
{rootBreadcrumb.label}
</span>
<OverflowText
label={rootBreadcrumb.label}
className='flex-1 text-[var(--text-body)] text-sm'
tooltipEnabled={false}
/>
)}
</button>
</PopoverAnchor>
Expand Down Expand Up @@ -703,10 +706,9 @@ const BreadcrumbLabel = memo(
<span
ref={ref}
className={cn(
'min-w-0 truncate text-[var(--text-body)]',
isOverflowing && overflowTextFadeClass,
isOverflowing &&
'group-hover:[-webkit-mask-image:none] group-hover:[mask-image:none] group-focus-visible:[-webkit-mask-image:none] group-focus-visible:[mask-image:none]'
overflowTextClipClass,
'text-[var(--text-body)]',
isOverflowing && overflowTextFadeClass
)}
>
{label}
Expand Down
24 changes: 4 additions & 20 deletions apps/sim/app/workspace/[workspaceId]/files/files.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import {
Folder,
FolderPlus,
Loader,
OverflowText,
Pencil,
Plus,
Trash,
Expand Down Expand Up @@ -1960,13 +1959,7 @@ export function Files() {
multiSelectValues={typeFilter}
onMultiSelectChange={setTypeFilter}
overlayLabel={typeDisplayLabel}
overlayContent={
<OverflowText
label={typeDisplayLabel}
className='block w-full text-[var(--text-primary)]'
tooltipEnabled={false}
/>
}
overlayContent={typeDisplayLabel}
showAllOption
allOptionLabel='All'
className='w-full'
Expand All @@ -1984,13 +1977,7 @@ export function Files() {
multiSelectValues={sizeFilter}
onMultiSelectChange={setSizeFilter}
overlayLabel={sizeDisplayLabel}
overlayContent={
<OverflowText
label={sizeDisplayLabel}
className='block w-full text-[var(--text-primary)]'
tooltipEnabled={false}
/>
}
overlayContent={sizeDisplayLabel}
showAllOption
allOptionLabel='All'
className='w-full'
Expand All @@ -2004,11 +1991,8 @@ export function Files() {
multiSelect
multiSelectValues={uploadedByFilter}
onMultiSelectChange={setUploadedByFilter}
overlayContent={
<span className='truncate text-[var(--text-primary)]'>
{uploadedByDisplayLabel}
</span>
}
overlayLabel={uploadedByDisplayLabel}
overlayContent={uploadedByDisplayLabel}
searchable
searchPlaceholder='Search members...'
showAllOption
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuItemLabel,
DropdownMenuSearchInput,
DropdownMenuSub,
DropdownMenuSubContent,
Expand Down Expand Up @@ -387,15 +388,15 @@ export function ResourceFolderTreeItems({
<DropdownMenuSub key={node.id}>
<DropdownMenuSubTrigger>
<Folder className='size-[14px]' />
<span>{node.name}</span>
<DropdownMenuItemLabel label={node.name} />
</DropdownMenuSubTrigger>
<DropdownMenuSubContent>
{folderType && (
<DropdownMenuItem
onClick={() => onSelect({ type: folderType, id: node.id, title: node.name })}
>
<Folder className='size-[14px]' />
<span>{node.name}</span>
<DropdownMenuItemLabel label={node.name} />
</DropdownMenuItem>
)}
<ResourceFolderTreeItems
Expand Down Expand Up @@ -535,7 +536,7 @@ export function ResourceMenuSections({
return (
<DropdownMenuItem key={type} onClick={() => onSelect(resourceFromItem(type, item))}>
<Icon className='size-[14px]' />
<span>{config.label}</span>
<DropdownMenuItemLabel label={config.label} />
</DropdownMenuItem>
)
}
Expand All @@ -544,7 +545,7 @@ export function ResourceMenuSections({
<DropdownMenuSub key={type}>
<DropdownMenuSubTrigger>
<Icon className='size-[14px]' />
<span>{config.label}</span>
<DropdownMenuItemLabel label={config.label} />
</DropdownMenuSubTrigger>
<DropdownMenuSubContent className={subContentClassName}>
{section ? (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,7 @@
'use client'

import { useCallback, useEffect, useEffectEvent, useMemo, useRef, useState } from 'react'
import {
Badge,
ChipCombobox,
ChipConfirmModal,
chipContentLabelClass,
cn,
OverflowText,
} from '@sim/emcn'
import { Badge, ChipCombobox, ChipConfirmModal, chipContentLabelClass, cn } from '@sim/emcn'
import {
ChevronDown,
ChevronUp,
Expand Down Expand Up @@ -754,13 +747,7 @@ export function Document({
setSelectedChunks(new Set())
}}
overlayLabel={enabledDisplayLabel}
overlayContent={
<OverflowText
label={enabledDisplayLabel}
className='block w-full text-[var(--text-primary)]'
tooltipEnabled={false}
/>
}
overlayContent={enabledDisplayLabel}
showAllOption
allOptionLabel='All'
className='w-full'
Expand Down
36 changes: 5 additions & 31 deletions apps/sim/app/workspace/[workspaceId]/logs/logs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1473,11 +1473,7 @@ function LogsFilterPanel({ searchQuery, onSearchQueryChange }: LogsFilterPanelPr
style={{ backgroundColor: selectedStatusColor, width: 8, height: 8 }}
/>
)}
<OverflowText
label={statusDisplayLabel}
className='block flex-1'
tooltipEnabled={false}
/>
<span className='min-w-0 flex-1'>{statusDisplayLabel}</span>
</span>
}
showAllOption
Expand All @@ -1500,11 +1496,7 @@ function LogsFilterPanel({ searchQuery, onSearchQueryChange }: LogsFilterPanelPr
{selectedWorkflow && (
<Workflow className='size-[14px] flex-shrink-0 text-[var(--text-icon)]' />
)}
<OverflowText
label={workflowDisplayLabel}
className='block flex-1'
tooltipEnabled={false}
/>
<span className='min-w-0 flex-1'>{workflowDisplayLabel}</span>
</span>
}
searchable
Expand All @@ -1524,13 +1516,7 @@ function LogsFilterPanel({ searchQuery, onSearchQueryChange }: LogsFilterPanelPr
onMultiSelectChange={setFolderIds}
placeholder='All folders'
overlayLabel={folderDisplayLabel}
overlayContent={
<OverflowText
label={folderDisplayLabel}
className='block w-full text-[var(--text-primary)]'
tooltipEnabled={false}
/>
}
overlayContent={folderDisplayLabel}
searchable
searchPlaceholder='Search folders...'
showAllOption
Expand All @@ -1548,13 +1534,7 @@ function LogsFilterPanel({ searchQuery, onSearchQueryChange }: LogsFilterPanelPr
onMultiSelectChange={setTriggers}
placeholder='All triggers'
overlayLabel={triggerDisplayLabel}
overlayContent={
<OverflowText
label={triggerDisplayLabel}
className='block w-full text-[var(--text-primary)]'
tooltipEnabled={false}
/>
}
overlayContent={triggerDisplayLabel}
searchable
searchPlaceholder='Search triggers...'
showAllOption
Expand All @@ -1572,13 +1552,7 @@ function LogsFilterPanel({ searchQuery, onSearchQueryChange }: LogsFilterPanelPr
onChange={handleTimeRangeChange}
placeholder='All time'
overlayLabel={timeDisplayLabel}
overlayContent={
<OverflowText
label={timeDisplayLabel}
className='block w-full text-[var(--text-primary)]'
tooltipEnabled={false}
/>
}
overlayContent={timeDisplayLabel}
className='w-full'
maxHeight={320}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuItemLabel,
DropdownMenuTrigger,
} from '@sim/emcn'
import { Columns3, Eye, EyeOff } from '@sim/emcn/icons'
Expand Down Expand Up @@ -166,11 +167,10 @@ function ColumnToggleRow({ label, visible, partial, indented, onToggle }: Column
)}
/>
</span>
<span
className={cn('min-w-0 flex-1 truncate text-left', !showing && 'text-[var(--text-muted)]')}
>
{label}
</span>
<DropdownMenuItemLabel
label={label}
className={cn('text-left', !showing && 'text-[var(--text-muted)]')}
/>
</DropdownMenuItem>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,11 @@ export const ViewsMenu = memo(function ViewsMenu({
onMouseLeave={scheduleClose}
className={cn(chipVariants(), 'max-w-[220px]')}
>
<span className={chipContentLabelClass}>{label}</span>
<OverflowText
label={label}
className={cn('flex-1', chipContentLabelClass)}
focusTarget='nearest-interactive'
/>
<ChipChevronDown />
</button>
</PopoverAnchor>
Expand Down
Loading
Loading