diff --git a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/column-config-sidebar/column-config-sidebar.tsx b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/column-config-sidebar/column-config-sidebar.tsx index 12aa2fdf5c..e433cfa5a6 100644 --- a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/column-config-sidebar/column-config-sidebar.tsx +++ b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/column-config-sidebar/column-config-sidebar.tsx @@ -1,13 +1,24 @@ 'use client' -import type React from 'react' import { useState } from 'react' import { toError } from '@sim/utils/errors' -import { X } from 'lucide-react' -import { Button, ChipCombobox, FieldDivider, Input, Label, Switch, toast } from '@/components/emcn' +import { + Button, + ChipCombobox, + ChipInput, + FieldDivider, + Label, + Switch, + toast, +} from '@/components/emcn' +import { X } from '@/components/emcn/icons' import { findValidationIssue, isValidationError } from '@/lib/api/client/errors' import { cn } from '@/lib/core/utils/cn' import type { ColumnDefinition } from '@/lib/table' +import { + FieldError, + RequiredLabel, +} from '@/app/workspace/[workspaceId]/tables/[tableId]/components/sidebar-fields' import { useAddTableColumn, useUpdateColumn } from '@/hooks/queries/tables' import { PLAIN_COLUMN_TYPE_OPTIONS } from './column-types' @@ -169,7 +180,7 @@ function ColumnConfigBody({
Column name - { @@ -178,6 +189,7 @@ function ColumnConfigBody({ }} spellCheck={false} autoComplete='off' + error={Boolean((showValidation && !trimmedName) || nameError)} aria-invalid={(showValidation && !trimmedName) || nameError ? true : undefined} /> {showValidation && !trimmedName && } @@ -228,16 +240,3 @@ function ColumnConfigBody({
) } - -function RequiredLabel({ htmlFor, children }: { htmlFor?: string; children: React.ReactNode }) { - return ( - - ) -} - -function FieldError({ message }: { message: string }) { - return

{message}

-} diff --git a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/enrichments-sidebar/enrichment-config.tsx b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/enrichments-sidebar/enrichment-config.tsx index 3c30675fa3..ff0b0cb1b4 100644 --- a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/enrichments-sidebar/enrichment-config.tsx +++ b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/enrichments-sidebar/enrichment-config.tsx @@ -7,16 +7,15 @@ import { Badge, Button, ChipCombobox, + ChipInput, CollapsibleCard, FieldDivider, - Input, Label, Switch, toast, } from '@/components/emcn' import { ArrowLeft, X } from '@/components/emcn/icons' import type { AddWorkflowGroupBodyInput } from '@/lib/api/contracts/tables' -import { cn } from '@/lib/core/utils/cn' import type { ColumnDefinition, WorkflowGroup, WorkflowGroupOutput } from '@/lib/table' import { deriveOutputColumnName } from '@/lib/table/column-naming' import type { EnrichmentConfig as EnrichmentDef } from '@/enrichments/types' @@ -280,12 +279,10 @@ export function EnrichmentConfig({ onChange={(columnName: string) => setInputMappings((prev) => ({ ...prev, [input.id]: columnName })) } - error={ - showValidation && input.required && !inputMappings[input.id] - ? 'Required' - : null - } /> + {showValidation && input.required && !inputMappings[input.id] && ( +

Required

+ )} ))}
@@ -317,14 +314,14 @@ export function EnrichmentConfig({ } > - setOutputNames((prev) => ({ ...prev, [output.id]: e.target.value })) } spellCheck={false} autoComplete='off' - className={cn(outErr && 'border-[var(--text-error)]')} + error={Boolean(outErr)} /> {outErr &&

{outErr}

} diff --git a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/enrichments-sidebar/enrichments-sidebar.tsx b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/enrichments-sidebar/enrichments-sidebar.tsx index a2575b4b43..01b016b4ce 100644 --- a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/enrichments-sidebar/enrichments-sidebar.tsx +++ b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/enrichments-sidebar/enrichments-sidebar.tsx @@ -1,7 +1,7 @@ 'use client' import { useState } from 'react' -import { Input } from '@/components/emcn' +import { Button, ChipInput } from '@/components/emcn' import { Search, X } from '@/components/emcn/icons' import { cn } from '@/lib/core/utils/cn' import type { ColumnDefinition, WorkflowGroup } from '@/lib/table' @@ -74,14 +74,15 @@ function EnrichmentsSidebarBody({

Enrichment

- +

@@ -119,28 +120,26 @@ function EnrichmentsSidebarBody({

Enrichments

- +
-
- - setQuery(e.target.value)} - placeholder='Search' - spellCheck={false} - autoComplete='off' - className='pl-7' - /> -
+ setQuery(e.target.value)} + placeholder='Search' + spellCheck={false} + autoComplete='off' + />
diff --git a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/index.ts b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/index.ts index 34b5f41f5f..02d4710b13 100644 --- a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/index.ts +++ b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/index.ts @@ -4,6 +4,7 @@ export * from './enrichments-sidebar' export * from './new-column-dropdown' export * from './row-modal' export * from './run-status-control' +export * from './sidebar-fields' export * from './table-action-bar' export * from './table-filter' export * from './table-grid' diff --git a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/sidebar-fields/index.ts b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/sidebar-fields/index.ts new file mode 100644 index 0000000000..b8175522f1 --- /dev/null +++ b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/sidebar-fields/index.ts @@ -0,0 +1 @@ +export { FieldError, RequiredLabel } from './sidebar-fields' diff --git a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/sidebar-fields/sidebar-fields.tsx b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/sidebar-fields/sidebar-fields.tsx new file mode 100644 index 0000000000..d08ad20bbf --- /dev/null +++ b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/sidebar-fields/sidebar-fields.tsx @@ -0,0 +1,30 @@ +'use client' + +import type React from 'react' +import { Label } from '@/components/emcn' + +/** + * Field label with a trailing required marker, matching the sidebar field + * rhythm shared by the column-config and workflow sidebars. + */ +export function RequiredLabel({ + htmlFor, + children, +}: { + htmlFor?: string + children: React.ReactNode +}) { + return ( + + ) +} + +/** + * Inline validation error rendered under a sidebar field. + */ +export function FieldError({ message }: { message: string }) { + return

{message}

+} diff --git a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-action-bar/table-action-bar.tsx b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-action-bar/table-action-bar.tsx index c29a140d29..eff97d3e89 100644 --- a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-action-bar/table-action-bar.tsx +++ b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-action-bar/table-action-bar.tsx @@ -1,5 +1,6 @@ 'use client' +import type React from 'react' import { AnimatePresence, domAnimation, LazyMotion, m } from 'framer-motion' import { Button, Tooltip } from '@/components/emcn' import { Eye, PlayOutline, RefreshCw, Square } from '@/components/emcn/icons' @@ -98,71 +99,35 @@ export function TableActionBar({
{showPlay && ( - - - - - {playLabel} - + + + )} {showRefresh && ( - - - - - {refreshLabel} - + + + )} {runningCount > 0 && ( - - - - - {stopLabel} - + + + )} {onViewExecution && ( - - - - - View execution - + + + )}
@@ -172,3 +137,34 @@ export function TableActionBar({ ) } + +interface ActionIconButtonProps { + /** Tooltip text, also used as the button's accessible label. */ + label: string + onClick: () => void + disabled: boolean + children: React.ReactNode +} + +/** + * Tooltip-wrapped icon button sharing the action bar's brand-hover chrome, + * so the chrome string lives in one place. + */ +function ActionIconButton({ label, onClick, disabled, children }: ActionIconButtonProps) { + return ( + + + + + {label} + + ) +} diff --git a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-filter/table-filter.tsx b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-filter/table-filter.tsx index 7b0737f6fd..adabe54f65 100644 --- a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-filter/table-filter.tsx +++ b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-filter/table-filter.tsx @@ -2,24 +2,13 @@ import { memo, useCallback, useMemo, useRef, useState } from 'react' import { generateShortId } from '@sim/utils/id' -import { X } from 'lucide-react' -import { - Button, - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, -} from '@/components/emcn' -import { ChevronDown, Plus } from '@/components/emcn/icons' +import { Button, ChipDropdown, ChipInput } from '@/components/emcn' +import { Plus, X } from '@/components/emcn/icons' import type { ColumnDefinition, Filter, FilterRule } from '@/lib/table' import { getColumnId } from '@/lib/table/column-keys' import { COMPARISON_OPERATORS, VALUELESS_OPERATORS } from '@/lib/table/query-builder/constants' import { filterRulesToFilter, filterToRules } from '@/lib/table/query-builder/converters' -const OPERATOR_LABELS = Object.fromEntries( - COMPARISON_OPERATORS.map((op) => [op.value, op.label]) -) as Record - interface TableFilterProps { columns: ColumnDefinition[] filter: Filter | null @@ -163,67 +152,49 @@ const FilterRuleRow = memo(function FilterRuleRow({ )} - - - - - - {columns.map((col) => ( - onUpdate(rule.id, 'column', col.value)} - > - {col.label} - - ))} - - - - - - - - - {COMPARISON_OPERATORS.map((op) => ( - onUpdate(rule.id, 'operator', op.value)} - > - {op.label} - - ))} - - + onUpdate(rule.id, 'column', value)} + placeholder='Column' + align='start' + matchTriggerWidth={false} + className='min-w-[100px]' + /> + + onUpdate(rule.id, 'operator', value)} + placeholder='Operator' + align='start' + matchTriggerWidth={false} + className='min-w-[90px]' + /> {VALUELESS_OPERATORS.has(rule.operator) ? (
) : ( - onUpdate(rule.id, 'value', e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter') onApply() }} placeholder='Enter a value' - className='h-[30px] flex-1 rounded-lg border border-[var(--border-1)] bg-[var(--surface-5)] px-2 text-[var(--text-secondary)] text-xs outline-none placeholder:text-[var(--text-subtle)] dark:bg-[var(--surface-4)]' + className='flex-1' /> )} - +
) }) diff --git a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-grid/cells/expanded-cell-popover.tsx b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-grid/cells/expanded-cell-popover.tsx index f499a41633..d610d99bc6 100644 --- a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-grid/cells/expanded-cell-popover.tsx +++ b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-grid/cells/expanded-cell-popover.tsx @@ -40,7 +40,6 @@ export function ExpandedCellPopover({ const rootRef = useRef(null) const textareaRef = useRef(null) const [rect, setRect] = useState<{ top: number; left: number; width: number } | null>(null) - const [draftValue, setDraftValue] = useState('') const target = useMemo(() => { if (!expandedCell) return null @@ -75,7 +74,6 @@ export function ExpandedCellPopover({ setRect(null) return } - setDraftValue(isEditable ? formatValueForInput(target.value, target.column.type) : '') const selector = `[data-table-scroll] [data-row-id="${target.row.id}"][data-col="${target.colIndex}"]` const el = document.querySelector(selector) if (!el) { @@ -86,7 +84,7 @@ export function ExpandedCellPopover({ setRect({ top: r.top, left: r.left, width: r.width }) // Focus textarea on open so typing works immediately. requestAnimationFrame(() => textareaRef.current?.focus()) - }, [expandedCell, target, isEditable]) + }, [expandedCell, target]) const onCloseEvent = useEffectEvent(onClose) @@ -136,23 +134,6 @@ export function ExpandedCellPopover({ ? Math.max(VIEWPORT_PAD, window.innerHeight - EXPANDED_CELL_HEIGHT - VIEWPORT_PAD) : rect.top - const handleSave = () => { - if (!isEditable) return - // `displayToStorage` only normalizes dates — it returns null for anything else. - // Fall back to the raw draft for non-date columns, matching the inline editor. - const raw = displayToStorage(draftValue) ?? draftValue - const cleaned = cleanCellValue(raw, target.column) - onSave(target.row.id, target.column.key, cleaned, 'blur') - onClose() - } - - const handleTextareaKeyDown = (e: React.KeyboardEvent) => { - if (e.key === 'Enter' && !e.shiftKey) { - e.preventDefault() - handleSave() - } - } - return (
{isEditable ? ( - <> -