mirror of
https://github.com/simstudioai/sim.git
synced 2026-01-09 15:07:55 -05:00
feat(sidebar-controls): added ability to expand on hover (#343)
This commit is contained in:
committed by
Emir Karabeg
parent
a92ee8bf46
commit
d79cad4c52
@@ -26,13 +26,7 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
|
||||
validation.workflow.deployedState as any
|
||||
)
|
||||
}
|
||||
|
||||
logger.info(`[${requestId}] Retrieved status for workflow: ${id}`, {
|
||||
isDeployed: validation.workflow.isDeployed,
|
||||
isPublished: validation.workflow.isPublished,
|
||||
needsRedeployment,
|
||||
})
|
||||
|
||||
|
||||
return createSuccessResponse({
|
||||
isDeployed: validation.workflow.isDeployed,
|
||||
deployedAt: validation.workflow.deployedAt,
|
||||
|
||||
@@ -140,6 +140,7 @@
|
||||
|
||||
/* Custom Animations */
|
||||
@layer utilities {
|
||||
|
||||
/* Animation containment to avoid layout shifts */
|
||||
.animation-container {
|
||||
contain: paint layout style;
|
||||
@@ -150,9 +151,11 @@
|
||||
0% {
|
||||
box-shadow: 0 0 0 0 hsl(var(--border));
|
||||
}
|
||||
|
||||
50% {
|
||||
box-shadow: 0 0 0 8px hsl(var(--border));
|
||||
}
|
||||
|
||||
100% {
|
||||
box-shadow: 0 0 0 0 hsl(var(--border));
|
||||
}
|
||||
@@ -195,6 +198,7 @@
|
||||
0% {
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
@@ -202,12 +206,11 @@
|
||||
|
||||
@keyframes orbit {
|
||||
0% {
|
||||
transform: rotate(calc(var(--angle) * 1deg)) translateY(calc(var(--radius) * 1px))
|
||||
rotate(calc(var(--angle) * -1deg));
|
||||
transform: rotate(calc(var(--angle) * 1deg)) translateY(calc(var(--radius) * 1px)) rotate(calc(var(--angle) * -1deg));
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: rotate(calc(var(--angle) * 1deg + 360deg)) translateY(calc(var(--radius) * 1px))
|
||||
rotate(calc((var(--angle) * -1deg) - 360deg));
|
||||
transform: rotate(calc(var(--angle) * 1deg + 360deg)) translateY(calc(var(--radius) * 1px)) rotate(calc((var(--angle) * -1deg) - 360deg));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -215,14 +218,17 @@
|
||||
from {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
to {
|
||||
transform: translateX(calc(-100% - var(--gap)));
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes marquee-vertical {
|
||||
from {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
to {
|
||||
transform: translateY(calc(-100% - var(--gap)));
|
||||
}
|
||||
@@ -234,30 +240,27 @@
|
||||
|
||||
.streaming-effect::after {
|
||||
content: '';
|
||||
@apply pointer-events-none absolute top-0 left-0 h-full w-full;
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
rgba(128, 128, 128, 0) 0%,
|
||||
rgba(128, 128, 128, 0.1) 50%,
|
||||
rgba(128, 128, 128, 0) 100%
|
||||
);
|
||||
@apply pointer-events-none absolute left-0 top-0 h-full w-full;
|
||||
background: linear-gradient(90deg,
|
||||
rgba(128, 128, 128, 0) 0%,
|
||||
rgba(128, 128, 128, 0.1) 50%,
|
||||
rgba(128, 128, 128, 0) 100%);
|
||||
animation: code-shimmer 1.5s infinite;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.dark .streaming-effect::after {
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
rgba(180, 180, 180, 0) 0%,
|
||||
rgba(180, 180, 180, 0.1) 50%,
|
||||
rgba(180, 180, 180, 0) 100%
|
||||
);
|
||||
background: linear-gradient(90deg,
|
||||
rgba(180, 180, 180, 0) 0%,
|
||||
rgba(180, 180, 180, 0.1) 50%,
|
||||
rgba(180, 180, 180, 0) 100%);
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
@@ -270,8 +273,10 @@
|
||||
|
||||
/* Dark mode error badge styling */
|
||||
.dark .error-badge {
|
||||
background-color: hsl(0, 70%, 20%) !important; /* Darker red background for dark mode */
|
||||
color: hsl(0, 0%, 100%) !important; /* Pure white text for better contrast */
|
||||
background-color: hsl(0, 70%, 20%) !important;
|
||||
/* Darker red background for dark mode */
|
||||
color: hsl(0, 0%, 100%) !important;
|
||||
/* Pure white text for better contrast */
|
||||
}
|
||||
|
||||
/* Input Overrides */
|
||||
@@ -293,10 +298,12 @@ input[type='search']::-ms-clear {
|
||||
|
||||
/* Code Prompt Bar Placeholder Animation */
|
||||
@keyframes placeholder-pulse {
|
||||
|
||||
0%,
|
||||
100% {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
50% {
|
||||
opacity: 0.8;
|
||||
}
|
||||
@@ -319,3 +326,9 @@ input[type='search']::-ms-clear {
|
||||
.font-geist-mono {
|
||||
font-family: var(--font-geist-mono);
|
||||
}
|
||||
|
||||
/* Sidebar overlay styles */
|
||||
.main-content-overlay {
|
||||
z-index: 40;
|
||||
/* Higher z-index to appear above content */
|
||||
}
|
||||
@@ -76,7 +76,6 @@ const RUN_COUNT_OPTIONS = [1, 5, 10, 25, 50, 100]
|
||||
export function ControlBar() {
|
||||
const router = useRouter()
|
||||
const { data: session } = useSession()
|
||||
const { isCollapsed: isSidebarCollapsed } = useSidebarStore()
|
||||
|
||||
// Store hooks
|
||||
const {
|
||||
|
||||
@@ -14,8 +14,13 @@ import { ToolbarTabs } from './components/toolbar-tabs/toolbar-tabs'
|
||||
export function Toolbar() {
|
||||
const [activeTab, setActiveTab] = useState<BlockCategory>('blocks')
|
||||
const [searchQuery, setSearchQuery] = useState('')
|
||||
const [isCollapsed, setIsCollapsed] = useState(false)
|
||||
const { isCollapsed: isSidebarCollapsed } = useSidebarStore()
|
||||
const { mode, isExpanded } = useSidebarStore()
|
||||
// In hover mode, act as if sidebar is always collapsed for layout purposes
|
||||
const isSidebarCollapsed =
|
||||
mode === 'expanded' ? !isExpanded : mode === 'collapsed' || mode === 'hover'
|
||||
|
||||
// State to track if toolbar is open - independent of sidebar state
|
||||
const [isToolbarOpen, setIsToolbarOpen] = useState(true)
|
||||
|
||||
const blocks = useMemo(() => {
|
||||
const filteredBlocks = !searchQuery.trim() ? getBlocksByCategory(activeTab) : getAllBlocks()
|
||||
@@ -31,13 +36,14 @@ export function Toolbar() {
|
||||
})
|
||||
}, [searchQuery, activeTab])
|
||||
|
||||
if (isCollapsed) {
|
||||
// Show toolbar button when it's closed, regardless of sidebar state
|
||||
if (!isToolbarOpen) {
|
||||
return (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
onClick={() => setIsCollapsed(false)}
|
||||
className={`fixed transition-left duration-200 ${isSidebarCollapsed ? 'left-20' : 'left-64'} bottom-[18px] z-10 flex h-9 w-9 items-center justify-center rounded-lg bg-background text-muted-foreground hover:text-foreground hover:bg-accent border`}
|
||||
onClick={() => setIsToolbarOpen(true)}
|
||||
className={`fixed transition-all duration-200 ${isSidebarCollapsed ? 'left-20' : 'left-64'} bottom-[18px] z-10 flex h-9 w-9 items-center justify-center rounded-lg bg-background text-muted-foreground hover:text-foreground hover:bg-accent border`}
|
||||
>
|
||||
<PanelRight className="h-5 w-5" />
|
||||
<span className="sr-only">Open Toolbar</span>
|
||||
@@ -50,7 +56,7 @@ export function Toolbar() {
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`fixed transition-left duration-200 ${isSidebarCollapsed ? 'left-14' : 'left-60'} top-16 z-10 h-[calc(100vh-4rem)] w-60 border-r bg-background sm:block`}
|
||||
className={`fixed transition-all duration-200 ${isSidebarCollapsed ? 'left-14' : 'left-60'} top-16 z-10 h-[calc(100vh-4rem)] w-60 border-r bg-background sm:block`}
|
||||
>
|
||||
<div className="flex flex-col h-full">
|
||||
<div className="px-4 pt-4 pb-1 sticky top-0 bg-background z-20">
|
||||
@@ -89,7 +95,7 @@ export function Toolbar() {
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
onClick={() => setIsCollapsed(true)}
|
||||
onClick={() => setIsToolbarOpen(false)}
|
||||
className="absolute right-4 bottom-[18px] flex h-9 w-9 items-center justify-center rounded-lg text-muted-foreground hover:text-foreground hover:bg-accent"
|
||||
>
|
||||
<PanelLeftClose className="h-5 w-5" />
|
||||
|
||||
@@ -48,7 +48,10 @@ function WorkflowContent() {
|
||||
// State
|
||||
const [selectedEdgeId, setSelectedEdgeId] = useState<string | null>(null)
|
||||
const [isInitialized, setIsInitialized] = useState(false)
|
||||
const { isCollapsed: isSidebarCollapsed } = useSidebarStore()
|
||||
const { mode, isExpanded } = useSidebarStore()
|
||||
// In hover mode, act as if sidebar is always collapsed for layout purposes
|
||||
const isSidebarCollapsed =
|
||||
mode === 'expanded' ? !isExpanded : mode === 'collapsed' || mode === 'hover'
|
||||
|
||||
// Hooks
|
||||
const params = useParams()
|
||||
@@ -478,8 +481,8 @@ function WorkflowContent() {
|
||||
<div className="flex flex-col h-screen w-full overflow-hidden">
|
||||
<div className={`transition-all duration-200 ${isSidebarCollapsed ? 'ml-14' : 'ml-60'}`}>
|
||||
<ControlBar />
|
||||
<Toolbar />
|
||||
</div>
|
||||
<Toolbar />
|
||||
<div
|
||||
className={`flex-1 relative w-full h-full transition-all duration-200 ${isSidebarCollapsed ? 'pl-14' : 'pl-60'}`}
|
||||
>
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import { PanelRight } from 'lucide-react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { SidebarMode, useSidebarStore } from '@/stores/sidebar/store'
|
||||
|
||||
// This component ONLY controls sidebar state, not toolbar state
|
||||
export function SidebarControl() {
|
||||
const { mode, setMode, toggleExpanded, isExpanded } = useSidebarStore()
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
const handleModeChange = (value: SidebarMode) => {
|
||||
// When selecting expanded mode, ensure it's expanded
|
||||
if (value === 'expanded' && !isExpanded) {
|
||||
toggleExpanded()
|
||||
}
|
||||
|
||||
// Set the new mode
|
||||
setMode(value)
|
||||
setOpen(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="flex h-8 w-8 p-0 items-center justify-center rounded-md text-muted-foreground hover:bg-accent/50 cursor-pointer"
|
||||
>
|
||||
<PanelRight className="h-[18px] w-[18px] text-muted-foreground" />
|
||||
<span className="sr-only text-sm">Sidebar control</span>
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
className="w-44 p-0 shadow-md border overflow-hidden rounded-lg bg-background"
|
||||
side="top"
|
||||
align="start"
|
||||
sideOffset={5}
|
||||
>
|
||||
<div className="border-b py-[10px] px-4">
|
||||
<h4 className="text-xs font-[480] text-muted-foreground">Sidebar control</h4>
|
||||
</div>
|
||||
<div className="px-2 pt-1 pb-2">
|
||||
<div className="flex flex-col gap-[1px]">
|
||||
<button
|
||||
className={cn(
|
||||
'w-full text-left py-1.5 px-2 text-xs rounded hover:bg-accent/50 text-muted-foreground font-medium'
|
||||
)}
|
||||
onClick={() => handleModeChange('expanded')}
|
||||
>
|
||||
<span className="flex items-center">
|
||||
<span
|
||||
className={cn(
|
||||
'h-1 w-1 rounded-full mr-1.5',
|
||||
mode === 'expanded' ? 'bg-muted-foreground' : 'bg-transparent'
|
||||
)}
|
||||
></span>
|
||||
Expanded
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
className={cn(
|
||||
'w-full text-left py-1.5 px-2 text-xs rounded hover:bg-accent/50 text-muted-foreground font-medium'
|
||||
)}
|
||||
onClick={() => handleModeChange('collapsed')}
|
||||
>
|
||||
<span className="flex items-center">
|
||||
<span
|
||||
className={cn(
|
||||
'h-1 w-1 rounded-full mr-1.5',
|
||||
mode === 'collapsed' ? 'bg-muted-foreground' : 'bg-transparent'
|
||||
)}
|
||||
></span>
|
||||
Collapsed
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
className={cn(
|
||||
'w-full text-left py-1.5 px-2 text-xs rounded hover:bg-accent/50 text-muted-foreground font-medium'
|
||||
)}
|
||||
onClick={() => handleModeChange('hover')}
|
||||
>
|
||||
<span className="flex items-center">
|
||||
<span
|
||||
className={cn(
|
||||
'h-1 w-1 rounded-full mr-1.5',
|
||||
mode === 'hover' ? 'bg-muted-foreground' : 'bg-transparent'
|
||||
)}
|
||||
></span>
|
||||
Expand on hover
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
@@ -39,6 +39,7 @@ import { Skeleton } from '@/components/ui/skeleton'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import { useSession } from '@/lib/auth-client'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { useSidebarStore } from '@/stores/sidebar/store'
|
||||
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
|
||||
|
||||
interface Workspace {
|
||||
@@ -51,6 +52,7 @@ interface Workspace {
|
||||
interface WorkspaceHeaderProps {
|
||||
onCreateWorkflow: () => void
|
||||
isCollapsed?: boolean
|
||||
onDropdownOpenChange?: (isOpen: boolean) => void
|
||||
}
|
||||
|
||||
// New WorkspaceModal component
|
||||
@@ -225,8 +227,16 @@ function WorkspaceEditModal({
|
||||
)
|
||||
}
|
||||
|
||||
export function WorkspaceHeader({ onCreateWorkflow, isCollapsed }: WorkspaceHeaderProps) {
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
export function WorkspaceHeader({
|
||||
onCreateWorkflow,
|
||||
isCollapsed,
|
||||
onDropdownOpenChange,
|
||||
}: WorkspaceHeaderProps) {
|
||||
// Get sidebar store state to check current mode
|
||||
const { mode, workspaceDropdownOpen, setAnyModalOpen } = useSidebarStore()
|
||||
|
||||
// Keep local isOpen state in sync with the store (for internal component use)
|
||||
const [isOpen, setIsOpen] = useState(workspaceDropdownOpen)
|
||||
const { data: sessionData, isPending } = useSession()
|
||||
const [plan, setPlan] = useState('Free Plan')
|
||||
// Use client-side loading instead of isPending to avoid hydration mismatch
|
||||
@@ -419,6 +429,32 @@ export function WorkspaceHeader({ onCreateWorkflow, isCollapsed }: WorkspaceHead
|
||||
// Determine URL for workspace links
|
||||
const workspaceUrl = activeWorkspace ? `/w/${activeWorkspace.id}` : '/w'
|
||||
|
||||
// Notify parent component when dropdown opens/closes
|
||||
const handleDropdownOpenChange = (open: boolean) => {
|
||||
setIsOpen(open)
|
||||
// Inform the parent component about the dropdown state change
|
||||
if (onDropdownOpenChange) {
|
||||
onDropdownOpenChange(open)
|
||||
}
|
||||
}
|
||||
|
||||
// Special handling for click interactions in hover mode
|
||||
const handleTriggerClick = (e: React.MouseEvent) => {
|
||||
// When in hover mode, explicitly prevent bubbling for the trigger
|
||||
if (mode === 'hover') {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
// Toggle dropdown state
|
||||
handleDropdownOpenChange(!isOpen)
|
||||
}
|
||||
}
|
||||
|
||||
// Handle modal open/close state
|
||||
useEffect(() => {
|
||||
// Update the modal state in the store
|
||||
setAnyModalOpen(isWorkspaceModalOpen || isEditModalOpen || isDeleting)
|
||||
}, [isWorkspaceModalOpen, isEditModalOpen, isDeleting, setAnyModalOpen])
|
||||
|
||||
return (
|
||||
<div className="py-2 px-2">
|
||||
{/* Workspace Modal */}
|
||||
@@ -436,9 +472,15 @@ export function WorkspaceHeader({ onCreateWorkflow, isCollapsed }: WorkspaceHead
|
||||
workspace={editingWorkspace}
|
||||
/>
|
||||
|
||||
<DropdownMenu open={isOpen} onOpenChange={setIsOpen}>
|
||||
<DropdownMenu open={isOpen} onOpenChange={handleDropdownOpenChange}>
|
||||
<div
|
||||
className={`group relative rounded-md cursor-pointer ${isCollapsed ? 'flex justify-center' : ''}`}
|
||||
onClick={(e) => {
|
||||
// In hover mode, prevent clicks on the container from collapsing the sidebar
|
||||
if (mode === 'hover') {
|
||||
e.stopPropagation()
|
||||
}
|
||||
}}
|
||||
>
|
||||
{/* Hover background with consistent padding - only when not collapsed */}
|
||||
{!isCollapsed && <div className="absolute inset-0 rounded-md group-hover:bg-accent/50" />}
|
||||
@@ -456,7 +498,10 @@ export function WorkspaceHeader({ onCreateWorkflow, isCollapsed }: WorkspaceHead
|
||||
) : (
|
||||
<div className="relative">
|
||||
<DropdownMenuTrigger asChild>
|
||||
<div className="flex items-center px-2 py-[6px] relative z-10 w-full">
|
||||
<div
|
||||
className="flex items-center px-2 py-[6px] relative z-10 w-full"
|
||||
onClick={handleTriggerClick}
|
||||
>
|
||||
<div className="flex items-center gap-2 overflow-hidden cursor-pointer">
|
||||
<Link
|
||||
href={workspaceUrl}
|
||||
|
||||
@@ -28,6 +28,7 @@ import { useRegistryLoading } from '../../hooks/use-registry-loading'
|
||||
import { HelpModal } from './components/help-modal/help-modal'
|
||||
import { NavSection } from './components/nav-section/nav-section'
|
||||
import { SettingsModal } from './components/settings-modal/settings-modal'
|
||||
import { SidebarControl } from './components/sidebar-control/sidebar-control'
|
||||
import { WorkflowList } from './components/workflow-list/workflow-list'
|
||||
import { WorkspaceHeader } from './components/workspace-header/workspace-header'
|
||||
|
||||
@@ -46,7 +47,18 @@ export function Sidebar() {
|
||||
const pathname = usePathname()
|
||||
const [showSettings, setShowSettings] = useState(false)
|
||||
const [showHelp, setShowHelp] = useState(false)
|
||||
const { isCollapsed, toggleCollapsed } = useSidebarStore()
|
||||
const {
|
||||
mode,
|
||||
isExpanded,
|
||||
toggleExpanded,
|
||||
setMode,
|
||||
workspaceDropdownOpen,
|
||||
setWorkspaceDropdownOpen,
|
||||
isAnyModalOpen,
|
||||
setAnyModalOpen,
|
||||
} = useSidebarStore()
|
||||
const [isHovered, setIsHovered] = useState(false)
|
||||
const [explicitMouseEnter, setExplicitMouseEnter] = useState(false)
|
||||
|
||||
// Track when active workspace changes to ensure we refresh the UI
|
||||
useEffect(() => {
|
||||
@@ -56,6 +68,18 @@ export function Sidebar() {
|
||||
}
|
||||
}, [activeWorkspaceId])
|
||||
|
||||
// Update modal state in the store when settings or help modals open/close
|
||||
useEffect(() => {
|
||||
setAnyModalOpen(showSettings || showHelp)
|
||||
}, [showSettings, showHelp, setAnyModalOpen])
|
||||
|
||||
// Reset explicit mouse enter state when modal state changes
|
||||
useEffect(() => {
|
||||
if (isAnyModalOpen) {
|
||||
setExplicitMouseEnter(false)
|
||||
}
|
||||
}, [isAnyModalOpen])
|
||||
|
||||
// Separate regular workflows from temporary marketplace workflows
|
||||
const { regularWorkflows, tempWorkflows } = useMemo(() => {
|
||||
const regular: WorkflowMetadata[] = []
|
||||
@@ -129,16 +153,49 @@ export function Sidebar() {
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate sidebar visibility states
|
||||
// When in hover mode, sidebar is collapsed until hovered or workspace dropdown is open
|
||||
// When in expanded/collapsed mode, sidebar follows isExpanded state
|
||||
const isCollapsed =
|
||||
mode === 'collapsed' ||
|
||||
(mode === 'hover' &&
|
||||
((!isHovered && !workspaceDropdownOpen) || isAnyModalOpen || !explicitMouseEnter))
|
||||
// Only show overlay effect when in hover mode and actually being hovered or dropdown is open
|
||||
const showOverlay =
|
||||
mode === 'hover' &&
|
||||
((isHovered && !isAnyModalOpen && explicitMouseEnter) || workspaceDropdownOpen)
|
||||
|
||||
return (
|
||||
<aside
|
||||
className={clsx(
|
||||
'fixed inset-y-0 left-0 z-10 flex flex-col border-r bg-background sm:flex transition-width duration-200',
|
||||
isCollapsed ? 'w-14' : 'w-60'
|
||||
'fixed inset-y-0 left-0 z-10 flex flex-col border-r bg-background sm:flex transition-all duration-200',
|
||||
isCollapsed ? 'w-14' : 'w-60',
|
||||
showOverlay ? 'shadow-lg' : '',
|
||||
mode === 'hover' ? 'main-content-overlay' : ''
|
||||
)}
|
||||
onMouseEnter={() => {
|
||||
if (mode === 'hover' && !isAnyModalOpen) {
|
||||
setIsHovered(true)
|
||||
setExplicitMouseEnter(true)
|
||||
}
|
||||
}}
|
||||
onMouseLeave={() => {
|
||||
if (mode === 'hover') {
|
||||
setIsHovered(false)
|
||||
}
|
||||
}}
|
||||
style={{
|
||||
// When in hover mode and expanded, position above content without pushing it
|
||||
position: showOverlay ? 'fixed' : 'fixed',
|
||||
}}
|
||||
>
|
||||
{/* Workspace Header - Fixed at top */}
|
||||
<div className="flex-shrink-0">
|
||||
<WorkspaceHeader onCreateWorkflow={handleCreateWorkflow} isCollapsed={isCollapsed} />
|
||||
<WorkspaceHeader
|
||||
onCreateWorkflow={handleCreateWorkflow}
|
||||
isCollapsed={isCollapsed}
|
||||
onDropdownOpenChange={setWorkspaceDropdownOpen}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Main navigation - Fixed at top below header */}
|
||||
@@ -236,35 +293,23 @@ export function Sidebar() {
|
||||
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div
|
||||
onClick={toggleCollapsed}
|
||||
className="flex items-center justify-center rounded-md text-sm font-medium text-muted-foreground hover:bg-accent/50 cursor-pointer w-8 h-8 mx-auto"
|
||||
>
|
||||
<ChevronRight className="h-[18px] w-[18px]" />
|
||||
</div>
|
||||
<SidebarControl />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right">Expand</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex justify-between">
|
||||
{/* Help button on left */}
|
||||
{/* Sidebar control on left */}
|
||||
<SidebarControl />
|
||||
|
||||
{/* Help button on right */}
|
||||
<div
|
||||
onClick={() => setShowHelp(true)}
|
||||
className="flex items-center justify-center rounded-md px-1 py-1 text-sm font-medium text-muted-foreground hover:bg-accent/50 cursor-pointer"
|
||||
className="flex items-center justify-center rounded-md w-8 h-8 text-sm font-medium text-muted-foreground hover:bg-accent/50 cursor-pointer"
|
||||
>
|
||||
<HelpCircle className="h-[18px] w-[18px]" />
|
||||
<span className="sr-only">Help</span>
|
||||
</div>
|
||||
|
||||
{/* Collapse/Expand button on right */}
|
||||
<div
|
||||
onClick={toggleCollapsed}
|
||||
className="flex items-center justify-center rounded-md px-1 py-1 text-sm font-medium text-muted-foreground hover:bg-accent/50 cursor-pointer"
|
||||
>
|
||||
<ChevronLeft className="h-[18px] w-[18px]" />
|
||||
<span className="sr-only">Collapse</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -62,7 +62,9 @@ export default function Logs() {
|
||||
const [selectedLogIndex, setSelectedLogIndex] = useState<number>(-1)
|
||||
const [isSidebarOpen, setIsSidebarOpen] = useState(false)
|
||||
const selectedRowRef = useRef<HTMLTableRowElement | null>(null)
|
||||
const { isCollapsed: isSidebarCollapsed } = useSidebarStore()
|
||||
const { mode, isExpanded } = useSidebarStore()
|
||||
const isSidebarCollapsed =
|
||||
mode === 'expanded' ? !isExpanded : mode === 'collapsed' || mode === 'hover'
|
||||
|
||||
// Group logs by executionId to identify the last log in each group
|
||||
const executionGroups = useMemo(() => {
|
||||
|
||||
@@ -83,8 +83,6 @@ async function getTeamSeats(userId: string): Promise<number> {
|
||||
*/
|
||||
export async function checkUsageStatus(userId: string): Promise<UsageData> {
|
||||
try {
|
||||
logger.info('Starting usage status check for user', { userId })
|
||||
|
||||
// In development, always return permissive limits
|
||||
if (!isProd) {
|
||||
// Get actual usage from the database for display purposes
|
||||
|
||||
@@ -1,21 +1,43 @@
|
||||
import { create } from 'zustand'
|
||||
import { persist } from 'zustand/middleware'
|
||||
|
||||
export type SidebarMode = 'expanded' | 'collapsed' | 'hover'
|
||||
|
||||
interface SidebarState {
|
||||
isCollapsed: boolean
|
||||
toggleCollapsed: () => void
|
||||
setCollapsed: (collapsed: boolean) => void
|
||||
mode: SidebarMode
|
||||
isExpanded: boolean
|
||||
// Track workspace dropdown state
|
||||
workspaceDropdownOpen: boolean
|
||||
// Track if any modal is open
|
||||
isAnyModalOpen: boolean
|
||||
setMode: (mode: SidebarMode) => void
|
||||
toggleExpanded: () => void
|
||||
// Control workspace dropdown state
|
||||
setWorkspaceDropdownOpen: (isOpen: boolean) => void
|
||||
// Control modal state
|
||||
setAnyModalOpen: (isOpen: boolean) => void
|
||||
// Force sidebar expanded state without triggering loops
|
||||
forceExpanded: (expanded: boolean) => void
|
||||
}
|
||||
|
||||
export const useSidebarStore = create<SidebarState>()(
|
||||
persist(
|
||||
(set) => ({
|
||||
isCollapsed: false,
|
||||
toggleCollapsed: () => set((state) => ({ isCollapsed: !state.isCollapsed })),
|
||||
setCollapsed: (collapsed) => set({ isCollapsed: collapsed }),
|
||||
mode: 'expanded', // Default to expanded mode
|
||||
isExpanded: true, // Default to expanded state
|
||||
workspaceDropdownOpen: false, // Track if workspace dropdown is open
|
||||
isAnyModalOpen: false, // Track if any modal is open
|
||||
setMode: (mode) => set({ mode }),
|
||||
toggleExpanded: () => set((state) => ({ isExpanded: !state.isExpanded })),
|
||||
// Only update dropdown state without changing isExpanded
|
||||
setWorkspaceDropdownOpen: (isOpen) => set({ workspaceDropdownOpen: isOpen }),
|
||||
// Update modal state
|
||||
setAnyModalOpen: (isOpen) => set({ isAnyModalOpen: isOpen }),
|
||||
// Separate function to control expanded state
|
||||
forceExpanded: (expanded) => set({ isExpanded: expanded }),
|
||||
}),
|
||||
{
|
||||
name: 'sidebar-state',
|
||||
}
|
||||
)
|
||||
)
|
||||
)
|
||||
Reference in New Issue
Block a user