diff --git a/apps/sim/app/_styles/globals.css b/apps/sim/app/_styles/globals.css index be0629e5e..b94f9a2e5 100644 --- a/apps/sim/app/_styles/globals.css +++ b/apps/sim/app/_styles/globals.css @@ -50,8 +50,8 @@ @layer base { :root, .light { - --bg: #fdfdfd; /* main canvas - neutral near-white */ - --surface-1: #fcfcfc; /* sidebar, panels */ + --bg: #fefefe; /* main canvas - neutral near-white */ + --surface-1: #fefefe; /* sidebar, panels */ --surface-2: #ffffff; /* blocks, cards, modals - pure white */ --surface-3: #f7f7f7; /* popovers, headers */ --surface-4: #f5f5f5; /* buttons base */ @@ -70,6 +70,7 @@ --text-muted: #737373; --text-subtle: #8c8c8c; --text-inverse: #ffffff; + --text-muted-inverse: #a0a0a0; --text-error: #ef4444; /* Borders / dividers */ @@ -186,6 +187,7 @@ --text-muted: #787878; --text-subtle: #7d7d7d; --text-inverse: #1b1b1b; + --text-muted-inverse: #b3b3b3; --text-error: #ef4444; /* --border-strong: #303030; */ @@ -331,38 +333,38 @@ } ::-webkit-scrollbar-track { - background: var(--surface-1); + background: transparent; } ::-webkit-scrollbar-thumb { - background-color: var(--surface-7); + background-color: #c0c0c0; border-radius: var(--radius); } ::-webkit-scrollbar-thumb:hover { - background-color: var(--surface-7); + background-color: #a8a8a8; } /* Dark Mode Global Scrollbar */ .dark ::-webkit-scrollbar-track { - background: var(--surface-4); + background: transparent; } .dark ::-webkit-scrollbar-thumb { - background-color: var(--surface-7); + background-color: #5a5a5a; } .dark ::-webkit-scrollbar-thumb:hover { - background-color: var(--surface-7); + background-color: #6a6a6a; } * { scrollbar-width: thin; - scrollbar-color: var(--surface-7) var(--surface-1); + scrollbar-color: #c0c0c0 transparent; } .dark * { - scrollbar-color: var(--surface-7) var(--surface-4); + scrollbar-color: #5a5a5a transparent; } .copilot-scrollable { diff --git a/apps/sim/app/chat/components/message/components/markdown-renderer.tsx b/apps/sim/app/chat/components/message/components/markdown-renderer.tsx index 5ac058b44..ba69814cf 100644 --- a/apps/sim/app/chat/components/message/components/markdown-renderer.tsx +++ b/apps/sim/app/chat/components/message/components/markdown-renderer.tsx @@ -16,7 +16,7 @@ export function LinkWithPreview({ href, children }: { href: string; children: Re {children} - + {href} diff --git a/apps/sim/app/workspace/[workspaceId]/logs/components/logs-toolbar/components/notifications/notifications.tsx b/apps/sim/app/workspace/[workspaceId]/logs/components/logs-toolbar/components/notifications/notifications.tsx index ceda33549..e727a34ff 100644 --- a/apps/sim/app/workspace/[workspaceId]/logs/components/logs-toolbar/components/notifications/notifications.tsx +++ b/apps/sim/app/workspace/[workspaceId]/logs/components/logs-toolbar/components/notifications/notifications.tsx @@ -185,6 +185,10 @@ export function NotificationSettings({ const hasSubscriptions = filteredSubscriptions.length > 0 + // Compute form visibility synchronously to avoid empty state flash + // Show form if user explicitly opened it OR if loading is complete with no subscriptions + const displayForm = showForm || (!isLoading && !hasSubscriptions && !editingId) + const getSubscriptionsForTab = useCallback( (tab: NotificationType) => { return subscriptions.filter((s) => s.notificationType === tab) @@ -192,12 +196,6 @@ export function NotificationSettings({ [subscriptions] ) - useEffect(() => { - if (!isLoading && !hasSubscriptions && !editingId) { - setShowForm(true) - } - }, [isLoading, hasSubscriptions, editingId, activeTab]) - const resetForm = useCallback(() => { setFormData({ workflowIds: [], @@ -1210,7 +1208,7 @@ export function NotificationSettings({ ) const renderTabContent = () => { - if (showForm) { + if (displayForm) { return renderForm() } @@ -1279,7 +1277,7 @@ export function NotificationSettings({ - {showForm ? ( + {displayForm ? ( <> {hasSubscriptions && ( - +

{isImportingWorkspace ? 'Importing workspace...' : 'Import workspace'}

@@ -364,7 +364,7 @@ export function WorkspaceHeader({ - +

{isCreatingWorkspace ? 'Creating workspace...' : 'Create workspace'}

diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/sidebar.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/sidebar.tsx index 1b24b4caa..13fd4aa42 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/sidebar.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/sidebar.tsx @@ -529,7 +529,7 @@ export function Sidebar() { - +

{isImporting ? 'Importing workflow...' : 'Import workflow'}

@@ -544,7 +544,7 @@ export function Sidebar() { - +

{isCreatingFolder ? 'Creating folder...' : 'Create folder'}

@@ -559,7 +559,7 @@ export function Sidebar() { - +

{isCreatingWorkflow ? 'Creating workflow...' : 'Create workflow'}

diff --git a/apps/sim/components/emcn/components/index.ts b/apps/sim/components/emcn/components/index.ts index 9e9ca0414..7e821b356 100644 --- a/apps/sim/components/emcn/components/index.ts +++ b/apps/sim/components/emcn/components/index.ts @@ -57,6 +57,8 @@ export { type PopoverBackButtonProps, PopoverContent, type PopoverContentProps, + PopoverDivider, + type PopoverDividerProps, PopoverFolder, type PopoverFolderProps, PopoverItem, diff --git a/apps/sim/components/emcn/components/popover/popover.tsx b/apps/sim/components/emcn/components/popover/popover.tsx index 7de80bd38..c82e651b8 100644 --- a/apps/sim/components/emcn/components/popover/popover.tsx +++ b/apps/sim/components/emcn/components/popover/popover.tsx @@ -55,53 +55,102 @@ import { Check, ChevronLeft, ChevronRight, Search } from 'lucide-react' import { cn } from '@/lib/core/utils/cn' type PopoverSize = 'sm' | 'md' - -/** - * Shared base styles for all popover interactive items. - * Ensures consistent styling across items, folders, and back button. - */ -const POPOVER_ITEM_BASE_CLASSES = - 'flex min-w-0 cursor-pointer items-center gap-[8px] rounded-[6px] px-[6px] font-base text-[var(--text-primary)] disabled:pointer-events-none disabled:opacity-50 disabled:cursor-not-allowed' - -/** - * Size-specific styles for popover items. - * SM: 11px text, 22px height - * MD: 13px text, 26px height - */ -const POPOVER_ITEM_SIZE_CLASSES: Record = { - sm: 'h-[22px] text-[11px]', - md: 'h-[26px] text-[13px]', -} - -/** - * Size-specific icon classes for popover items. - */ -const POPOVER_ICON_SIZE_CLASSES: Record = { - sm: 'h-3 w-3', - md: 'h-3.5 w-3.5', -} - -/** - * Variant-specific active state styles for popover items. - */ -const POPOVER_ITEM_ACTIVE_CLASSES = { - secondary: 'bg-[var(--brand-secondary)] text-[var(--bg)] [&_svg]:text-[var(--bg)]', - default: - 'bg-[var(--surface-7)] dark:bg-[var(--surface-5)] text-[var(--text-primary)] [&_svg]:text-[var(--text-primary)]', -} - -/** - * Variant-specific hover state styles for popover items. - */ -const POPOVER_ITEM_HOVER_CLASSES = { - secondary: - 'hover:bg-[var(--brand-secondary)] hover:text-[var(--bg)] hover:[&_svg]:text-[var(--bg)]', - default: - 'hover:bg-[var(--surface-7)] dark:hover:bg-[var(--surface-5)] hover:text-[var(--text-primary)] hover:[&_svg]:text-[var(--text-primary)]', -} - +type PopoverColorScheme = 'default' | 'inverted' type PopoverVariant = 'default' | 'secondary' +/** + * Style constants for popover components. + * Organized by component type and property. + */ +const STYLES = { + /** Base classes shared by all interactive items */ + itemBase: + 'flex min-w-0 cursor-pointer items-center gap-[8px] rounded-[6px] px-[6px] font-base disabled:pointer-events-none disabled:opacity-50 disabled:cursor-not-allowed', + + /** Content container */ + content: 'px-[6px] py-[6px] rounded-[6px]', + + /** Size variants */ + size: { + sm: { item: 'h-[22px] text-[11px]', icon: 'h-3 w-3', section: 'px-[6px] py-[4px] text-[11px]' }, + md: { + item: 'h-[26px] text-[13px]', + icon: 'h-3.5 w-3.5', + section: 'px-[6px] py-[4px] text-[13px]', + }, + } satisfies Record, + + /** Color scheme variants */ + colorScheme: { + default: { + text: 'text-[var(--text-primary)]', + section: 'text-[var(--text-tertiary)]', + search: 'text-[var(--text-muted)]', + searchInput: 'text-[var(--text-primary)] placeholder:text-[var(--text-muted)]', + content: 'bg-[var(--surface-5)] text-foreground dark:bg-[var(--surface-3)]', + divider: 'border-[var(--border-1)]', + }, + inverted: { + text: 'text-white dark:text-[var(--text-primary)]', + section: 'text-[var(--text-muted-inverse)]', + search: 'text-[var(--text-muted-inverse)] dark:text-[var(--text-muted)]', + searchInput: + 'text-white placeholder:text-[var(--text-muted-inverse)] dark:text-[var(--text-primary)] dark:placeholder:text-[var(--text-muted)]', + content: 'bg-[#1b1b1b] text-white dark:bg-[var(--surface-3)] dark:text-foreground', + divider: 'border-[#363636] dark:border-[var(--border-1)]', + }, + } satisfies Record< + PopoverColorScheme, + { + text: string + section: string + search: string + searchInput: string + content: string + divider: string + } + >, + + /** Interactive state styles: default, secondary (brand), inverted (dark bg in light mode) */ + states: { + default: { + active: 'bg-[var(--border-1)] text-[var(--text-primary)] [&_svg]:text-[var(--text-primary)]', + hover: + 'hover:bg-[var(--border-1)] hover:text-[var(--text-primary)] hover:[&_svg]:text-[var(--text-primary)]', + }, + secondary: { + active: + 'bg-[var(--brand-secondary)] text-[var(--text-inverse)] [&_svg]:text-[var(--text-inverse)]', + hover: + 'hover:bg-[var(--brand-secondary)] hover:text-[var(--text-inverse)] dark:hover:text-[var(--text-inverse)] hover:[&_svg]:text-[var(--text-inverse)] dark:hover:[&_svg]:text-[var(--text-inverse)]', + }, + inverted: { + active: + 'bg-[#363636] text-white [&_svg]:text-white dark:bg-[var(--surface-5)] dark:text-[var(--text-primary)] dark:[&_svg]:text-[var(--text-primary)]', + hover: + 'hover:bg-[#363636] hover:text-white hover:[&_svg]:text-white dark:hover:bg-[var(--surface-5)] dark:hover:text-[var(--text-primary)] dark:hover:[&_svg]:text-[var(--text-primary)]', + }, + }, +} as const + +/** + * Gets the active/hover classes for a popover item. + * Uses variant for secondary, otherwise colorScheme determines default vs inverted. + */ +function getItemStateClasses( + variant: PopoverVariant, + colorScheme: PopoverColorScheme, + isActive: boolean +): string { + const state = isActive ? 'active' : 'hover' + + if (variant === 'secondary') { + return STYLES.states.secondary[state] + } + + return colorScheme === 'inverted' ? STYLES.states.inverted[state] : STYLES.states.default[state] +} + interface PopoverContextValue { openFolder: ( id: string, @@ -116,6 +165,7 @@ interface PopoverContextValue { onFolderSelect: (() => void) | null variant: PopoverVariant size: PopoverSize + colorScheme: PopoverColorScheme searchQuery: string setSearchQuery: (query: string) => void } @@ -143,23 +193,23 @@ export interface PopoverProps extends PopoverPrimitive.PopoverProps { * @default 'md' */ size?: PopoverSize + /** + * Color scheme for the popover + * - default: light background in light mode, dark in dark mode + * - inverted: dark background (#1b1b1b) in light mode, matches tooltip styling + * @default 'default' + */ + colorScheme?: PopoverColorScheme } /** * Root popover component. Manages open state and folder navigation context. - * - * @example - * ```tsx - * - * ... - * ... - * - * ``` */ const Popover: React.FC = ({ children, variant = 'default', size = 'md', + colorScheme = 'default', ...props }) => { const [currentFolder, setCurrentFolder] = React.useState(null) @@ -185,7 +235,7 @@ const Popover: React.FC = ({ setOnFolderSelect(null) }, []) - const contextValue: PopoverContextValue = React.useMemo( + const contextValue = React.useMemo( () => ({ openFolder, closeFolder, @@ -195,6 +245,7 @@ const Popover: React.FC = ({ onFolderSelect, variant, size, + colorScheme, searchQuery, setSearchQuery, }), @@ -206,6 +257,7 @@ const Popover: React.FC = ({ onFolderSelect, variant, size, + colorScheme, searchQuery, ] ) @@ -222,13 +274,6 @@ Popover.displayName = 'Popover' /** * Trigger element that opens/closes the popover when clicked. * Use asChild to render as a custom component. - * - * @example - * ```tsx - * - * - * - * ``` */ const PopoverTrigger = PopoverPrimitive.Trigger @@ -244,74 +289,48 @@ export interface PopoverContentProps 'side' | 'align' | 'sideOffset' | 'alignOffset' | 'collisionPadding' > { /** - * When true, renders the popover content inline instead of in a portal. - * Useful when used inside other portalled components (e.g. dialogs) - * where additional portals can interfere with scroll locking behavior. + * Renders content inline instead of in a portal. + * Useful inside dialogs where portals interfere with scroll locking. * @default false */ disablePortal?: boolean - /** - * Maximum height for the popover content in pixels - */ + /** Maximum height in pixels */ maxHeight?: number - /** - * Maximum width for the popover content in pixels. - * When provided, Popover will also enable default truncation for inner text and section headers. - */ + /** Maximum width in pixels. Enables text truncation when set. */ maxWidth?: number - /** - * Minimum width for the popover content in pixels - */ + /** Minimum width in pixels */ minWidth?: number /** - * Preferred side to display the popover + * Preferred side to display * @default 'bottom' */ side?: 'top' | 'right' | 'bottom' | 'left' /** - * Alignment of the popover relative to anchor + * Alignment relative to anchor * @default 'start' */ align?: 'start' | 'center' | 'end' - /** - * Offset from the anchor in pixels. - * Defaults to 22px for top side (to avoid covering cursor) and 10px for other sides. - */ + /** Offset from anchor. Defaults to 20px for top, 14px for other sides. */ sideOffset?: number /** - * Padding from viewport edges in pixels + * Padding from viewport edges * @default 8 */ collisionPadding?: number /** - * When true, adds a border to the popover content + * Adds border to content * @default false */ border?: boolean /** - * When true, the popover will flip to avoid collisions with viewport edges + * Flip to avoid viewport collisions * @default true */ avoidCollisions?: boolean } /** - * Shared styles for popover content container. - * Both sizes use same padding and 6px border radius. - */ -const POPOVER_CONTENT_CLASSES = 'px-[6px] py-[6px] rounded-[6px]' - -/** - * Popover content component with automatic positioning and collision detection. - * Wraps children in a styled container with scrollable area. - * - * @example - * ```tsx - * - * Item 1 - * Item 2 - * - * ``` + * Popover content with automatic positioning and collision detection. */ const PopoverContent = React.forwardRef< React.ElementRef, @@ -340,13 +359,10 @@ const PopoverContent = React.forwardRef< ) => { const context = React.useContext(PopoverContext) const size = context?.size || 'md' + const colorScheme = context?.colorScheme || 'default' - // Smart default offset: larger offset when rendering above to avoid covering cursor const effectiveSideOffset = sideOffset ?? (side === 'top' ? 20 : 14) - // Detect explicit width constraints provided by the consumer. - // When present, we enable default text truncation behavior for inner flexible items, - // so callers don't need to manually pass 'truncate' to every label. const hasUserWidthConstraint = maxWidth !== undefined || minWidth !== undefined || @@ -359,29 +375,21 @@ const PopoverContent = React.forwardRef< if (!container) return const { scrollHeight, clientHeight, scrollTop } = container - if (scrollHeight <= clientHeight) { - return - } + if (scrollHeight <= clientHeight) return const deltaY = event.deltaY const isScrollingDown = deltaY > 0 const isAtTop = scrollTop === 0 const isAtBottom = scrollTop + clientHeight >= scrollHeight - // If we're at the boundary and user keeps scrolling in that direction, - // let the event bubble so parent scroll containers can handle it. - if ((isScrollingDown && isAtBottom) || (!isScrollingDown && isAtTop)) { - return - } + if ((isScrollingDown && isAtBottom) || (!isScrollingDown && isAtTop)) return - // Otherwise, consume the wheel event and manually scroll the popover content. event.preventDefault() container.scrollTop += deltaY } const handleOpenAutoFocus = React.useCallback( (e: Event) => { - // Always prevent auto-focus to avoid flickering from focus-triggered repositioning e.preventDefault() onOpenAutoFocus?.(e) }, @@ -390,7 +398,6 @@ const PopoverContent = React.forwardRef< const handleCloseAutoFocus = React.useCallback( (e: Event) => { - // Always prevent auto-focus to avoid flickering from focus-triggered repositioning e.preventDefault() onCloseAutoFocus?.(e) }, @@ -412,11 +419,9 @@ const PopoverContent = React.forwardRef< onCloseAutoFocus={handleCloseAutoFocus} {...restProps} className={cn( - // will-change-transform creates a new GPU compositing layer to prevent paint flickering - 'z-[10000200] flex flex-col overflow-auto bg-[var(--surface-5)] text-foreground outline-none will-change-transform dark:bg-[var(--surface-3)]', - POPOVER_CONTENT_CLASSES, - // If width is constrained by the caller (prop or style), ensure inner flexible text truncates by default, - // and also truncate section headers. + 'z-[10000200] flex flex-col overflow-auto outline-none will-change-transform', + STYLES.colorScheme[colorScheme].content, + STYLES.content, hasUserWidthConstraint && '[&_.flex-1]:truncate [&_[data-popover-section]]:truncate', border && 'border border-[var(--border-1)]', className @@ -424,7 +429,6 @@ const PopoverContent = React.forwardRef< style={{ maxHeight: `${maxHeight || 400}px`, maxWidth: maxWidth !== undefined ? `${maxWidth}px` : 'calc(100vw - 16px)', - // Only enforce default min width when the user hasn't set width constraints minWidth: minWidth !== undefined ? `${minWidth}px` @@ -440,9 +444,7 @@ const PopoverContent = React.forwardRef< ) - if (disablePortal) { - return content - } + if (disablePortal) return content return {content} } @@ -453,83 +455,52 @@ PopoverContent.displayName = 'PopoverContent' export interface PopoverScrollAreaProps extends React.HTMLAttributes {} /** - * Scrollable area container for popover items. - * Use this to wrap items that should scroll within the popover. - * - * @example - * ```tsx - * - * - * Item 1 - * Item 2 - * - * - * ``` + * Scrollable container for popover items. */ const PopoverScrollArea = React.forwardRef( - ({ className, ...props }, ref) => { - return ( -
div:has([data-popover-section]):not(:first-child)]:mt-[6px]', - className - )} - ref={ref} - {...props} - /> - ) - } + ({ className, ...props }, ref) => ( +
div:has([data-popover-section]):not(:first-child)]:mt-[6px]', + className + )} + ref={ref} + {...props} + /> + ) ) PopoverScrollArea.displayName = 'PopoverScrollArea' export interface PopoverItemProps extends React.HTMLAttributes { - /** - * Whether this item is currently active/selected - */ + /** Whether this item is currently active/selected */ active?: boolean - /** - * If true, this item will only show when not inside any folder - */ + /** Only show when not inside any folder */ rootOnly?: boolean - /** - * Whether this item is disabled - */ + /** Whether this item is disabled */ disabled?: boolean /** - * Whether to show a checkmark when active + * Show checkmark when active * @default false */ showCheck?: boolean } /** - * Popover item component for individual items within a popover. - * - * @example - * ```tsx - * handleClick()}> - * - * Item label - * - * ``` + * Individual popover item with hover and active states. */ const PopoverItem = React.forwardRef( ( { className, active, rootOnly, disabled, showCheck = false, children, onClick, ...props }, ref ) => { - // Try to get context - if not available, we're outside Popover (shouldn't happen) const context = React.useContext(PopoverContext) const variant = context?.variant || 'default' const size = context?.size || 'md' + const colorScheme = context?.colorScheme || 'default' - // If rootOnly is true and we're in a folder, don't render - if (rootOnly && context?.isInFolder) { - return null - } + if (rootOnly && context?.isInFolder) return null const handleClick = (e: React.MouseEvent) => { if (disabled) { @@ -542,9 +513,10 @@ const PopoverItem = React.forwardRef( return (
( {...props} > {children} - {showCheck && active && ( - - )} + {showCheck && active && }
) } @@ -567,46 +537,27 @@ const PopoverItem = React.forwardRef( PopoverItem.displayName = 'PopoverItem' export interface PopoverSectionProps extends React.HTMLAttributes { - /** - * If true, this section will only show when not inside any folder - */ + /** Only show when not inside any folder */ rootOnly?: boolean } /** - * Size-specific styles for popover section headers. - * Shared: 6px padding, 4px vertical padding - */ -const POPOVER_SECTION_SIZE_CLASSES: Record = { - sm: 'px-[6px] py-[4px] text-[11px]', - md: 'px-[6px] py-[4px] text-[13px]', -} - -/** - * Popover section header component for grouping items with a title. - * - * @example - * ```tsx - * - * Section Title - * - * ``` + * Section header for grouping popover items. */ const PopoverSection = React.forwardRef( ({ className, rootOnly, ...props }, ref) => { const context = React.useContext(PopoverContext) const size = context?.size || 'md' + const colorScheme = context?.colorScheme || 'default' - // If rootOnly is true and we're in a folder, don't render - if (rootOnly && context?.isInFolder) { - return null - } + if (rootOnly && context?.isInFolder) return null return (
( PopoverSection.displayName = 'PopoverSection' export interface PopoverFolderProps extends Omit, 'children'> { - /** - * Unique identifier for the folder - */ + /** Unique folder identifier */ id: string - /** - * Display title for the folder - */ + /** Display title */ title: string - /** - * Icon to display before the title - */ + /** Icon before title */ icon?: React.ReactNode - /** - * Function to call when folder is opened (for lazy loading) - */ + /** Callback when folder opens (for lazy loading) */ onOpen?: () => void | Promise - /** - * Function to call when the folder title is selected (from within the folder view) - */ + /** Callback when folder title is selected from within folder view */ onSelect?: () => void - /** - * Children to render when folder is open - */ + /** Folder contents */ children?: React.ReactNode - /** - * Whether this item is currently active/selected - */ + /** Whether currently active/selected */ active?: boolean } /** - * Popover folder component that expands to show nested content. - * Automatically handles navigation and back button rendering. - * - * @example - * ```tsx - * }> - * Workflow 1 - * Workflow 2 - * - * ``` + * Expandable folder that shows nested content. */ const PopoverFolder = React.forwardRef( ({ className, id, title, icon, onOpen, onSelect, children, active, ...props }, ref) => { - const { openFolder, currentFolder, isInFolder, variant, size } = usePopoverContext() + const { openFolder, currentFolder, isInFolder, variant, size, colorScheme } = + usePopoverContext() - // Don't render if we're in a different folder - if (isInFolder && currentFolder !== id) { - return null - } + if (isInFolder && currentFolder !== id) return null + if (currentFolder === id) return <>{children} - // If we're in this folder, render its children - if (currentFolder === id) { - return <>{children} - } - - // Handle click anywhere on folder item const handleClick = (e: React.MouseEvent) => { e.stopPropagation() openFolder(id, title, onOpen, onSelect) } - // Otherwise, render as a clickable folder item return (
( > {icon} {title} - +
) } @@ -709,42 +630,23 @@ const PopoverFolder = React.forwardRef( PopoverFolder.displayName = 'PopoverFolder' export interface PopoverBackButtonProps extends React.HTMLAttributes { - /** - * Ref callback for the folder title element (when selectable) - */ + /** Ref callback for folder title element */ folderTitleRef?: (el: HTMLElement | null) => void - /** - * Whether the folder title is currently active/selected - */ + /** Whether folder title is active/selected */ folderTitleActive?: boolean - /** - * Callback when mouse enters the folder title - */ + /** Callback on folder title mouse enter */ onFolderTitleMouseEnter?: () => void } /** - * Back button component that appears when inside a folder. - * Automatically hidden when at root level. - * - * @example - * ```tsx - * - * - * - * // content - * - * - * ``` + * Back button shown inside folders. Hidden at root level. */ const PopoverBackButton = React.forwardRef( ({ className, folderTitleRef, folderTitleActive, onFolderTitleMouseEnter, ...props }, ref) => { - const { isInFolder, closeFolder, folderTitle, onFolderSelect, variant, size } = + const { isInFolder, closeFolder, folderTitle, onFolderSelect, variant, size, colorScheme } = usePopoverContext() - if (!isInFolder) { - return null - } + if (!isInFolder) return null return (
@@ -752,28 +654,27 @@ const PopoverBackButton = React.forwardRef - + Back
{folderTitle && onFolderSelect && (
{folderTitle} @@ -805,43 +707,20 @@ PopoverBackButton.displayName = 'PopoverBackButton' export interface PopoverSearchProps extends React.HTMLAttributes { /** - * Placeholder text for the search input + * Placeholder text * @default 'Search...' */ placeholder?: string - /** - * Callback when search query changes - */ + /** Callback when query changes */ onValueChange?: (value: string) => void } /** - * Size-specific styles for popover search container. - * Shared: padding - */ -const POPOVER_SEARCH_SIZE_CLASSES: Record = { - sm: 'px-[8px] py-[6px] text-[11px]', - md: 'px-[8px] py-[6px] text-[13px]', -} - -/** - * Search input component for filtering popover items. - * - * @example - * ```tsx - * - * - * - * - * // items - * - * - * - * ``` + * Search input for filtering popover items. */ const PopoverSearch = React.forwardRef( ({ className, placeholder = 'Search...', onValueChange, ...props }, ref) => { - const { searchQuery, setSearchQuery, size } = usePopoverContext() + const { searchQuery, setSearchQuery, size, colorScheme } = usePopoverContext() const inputRef = React.useRef(null) const handleChange = (e: React.ChangeEvent) => { @@ -857,18 +736,19 @@ const PopoverSearch = React.forwardRef( }, [setSearchQuery, onValueChange]) return ( -
+
( PopoverSearch.displayName = 'PopoverSearch' +export interface PopoverDividerProps extends React.HTMLAttributes { + /** Only show when not inside any folder */ + rootOnly?: boolean +} + +/** + * Horizontal divider for separating popover sections. + */ +const PopoverDivider = React.forwardRef( + ({ className, rootOnly, ...props }, ref) => { + const context = React.useContext(PopoverContext) + const colorScheme = context?.colorScheme || 'default' + + if (rootOnly && context?.isInFolder) return null + + return ( +
+ ) + } +) + +PopoverDivider.displayName = 'PopoverDivider' + export { Popover, PopoverTrigger, @@ -893,7 +801,8 @@ export { PopoverFolder, PopoverBackButton, PopoverSearch, + PopoverDivider, usePopoverContext, } -export type { PopoverSize } +export type { PopoverSize, PopoverColorScheme } diff --git a/apps/sim/components/emcn/components/tooltip/tooltip.tsx b/apps/sim/components/emcn/components/tooltip/tooltip.tsx index 705d7a376..f3c42c0c9 100644 --- a/apps/sim/components/emcn/components/tooltip/tooltip.tsx +++ b/apps/sim/components/emcn/components/tooltip/tooltip.tsx @@ -45,13 +45,13 @@ const Content = React.forwardRef< collisionPadding={8} avoidCollisions={true} className={cn( - 'z-[10000300] rounded-[3px] bg-black px-[7.5px] py-[6px] font-base text-white text-xs shadow-md dark:bg-white dark:text-black', + 'z-[10000300] rounded-[4px] bg-[#1b1b1b] px-[8px] py-[3.5px] font-base text-white text-xs shadow-sm dark:bg-[#fdfdfd] dark:text-black', className )} {...props} > {props.children} - + ))